<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Sinan Aktepe Blog</title>
        <link>https://saktepe.com/blog</link>
        <description>Sinan Aktepe Blog</description>
        <lastBuildDate>Mon, 20 Apr 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[What 7 Years of Flutter Taught Me About Production Apps]]></title>
            <link>https://saktepe.com/blog/7-years-flutter-production-apps</link>
            <guid>https://saktepe.com/blog/7-years-flutter-production-apps</guid>
            <pubDate>Mon, 20 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Lessons from building high-traffic Flutter apps, plugins, real-time products, and maintainable mobile architectures.]]></description>
            <content:encoded><![CDATA[<p>Flutter was the tool that let me move fast. Production was the place that taught me where speed becomes expensive.</p>
<p>After more than seven years of building mobile applications, plugins, high-traffic news apps, SaaS products, and real-time experiences, I no longer think of Flutter as just a UI framework. I think of it as a system that rewards clear boundaries, boring reliability, and careful state design.</p>
<!-- -->
<p>This is not a list of tricks. It is a list of lessons I keep coming back to when a Flutter app has to survive real users, real traffic, real devices, changing requirements, native SDKs, backend contracts, and the slow pressure of time.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-fast-development-is-not-the-same-as-sustainable-development">1. Fast development is not the same as sustainable development<a href="https://saktepe.com/blog/7-years-flutter-production-apps#1-fast-development-is-not-the-same-as-sustainable-development" class="hash-link" aria-label="Direct link to 1. Fast development is not the same as sustainable development" title="Direct link to 1. Fast development is not the same as sustainable development" translate="no">​</a></h2>
<p>Flutter makes the first version feel wonderfully close. You can turn an idea into screens quickly. Hot reload keeps the loop short. The widget model is expressive. The package ecosystem gets you far before you need to write much infrastructure yourself.</p>
<p>That speed is real, and it is one of the reasons I still enjoy working with Flutter. But production changes the question.</p>
<p>At the beginning, the question is usually:</p>
<ul>
<li class="">Can we build this?</li>
<li class="">Can we ship this?</li>
<li class="">Can we make the flow work?</li>
</ul>
<p>Later, the question becomes:</p>
<ul>
<li class="">Can we change this without breaking five other things?</li>
<li class="">Can we debug this when it fails on a device we do not own?</li>
<li class="">Can the app keep feeling fast after the product doubles in scope?</li>
<li class="">Can a new developer understand where the next change belongs?</li>
</ul>
<p>Those are different questions. A codebase optimized only for the first version often becomes hostile to the second, third, and fourth version.</p>
<p>The lesson is simple: speed is useful, but only when it does not steal from the future. A production Flutter app needs enough structure to keep product speed alive after the easy part is over.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-architecture-is-about-change-not-folders">2. Architecture is about change, not folders<a href="https://saktepe.com/blog/7-years-flutter-production-apps#2-architecture-is-about-change-not-folders" class="hash-link" aria-label="Direct link to 2. Architecture is about change, not folders" title="Direct link to 2. Architecture is about change, not folders" translate="no">​</a></h2>
<p>Architecture is not a folder structure.</p>
<p>You can create <code>features</code>, <code>data</code>, <code>domain</code>, and <code>presentation</code> folders and still have a messy app. You can also build a small, clean app without naming every pattern. The names are not the architecture. The boundaries are.</p>
<p>For me, architecture starts becoming useful when it answers practical questions:</p>
<ul>
<li class="">Where does this behavior belong?</li>
<li class="">Which layer owns this decision?</li>
<li class="">What should change when an API response changes?</li>
<li class="">What should not change when the UI changes?</li>
<li class="">Can I test this without rendering the whole screen?</li>
</ul>
<p>Clean Architecture, MVVM, repositories, use cases, Cubits, controllers, and services are all tools. They become valuable when they reduce the cost of change. They become ceremony when they only make the code look serious.</p>
<p>In production, I care less about whether the structure looks impressive and more about whether the next change has a clear place to go.</p>
<p>The best architecture is not the one with the most layers. It is the one where dependencies point in predictable directions, business rules are not trapped inside widgets, and the codebase can grow without every feature becoming everyone else's problem.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-state-management-starts-before-choosing-a-package">3. State management starts before choosing a package<a href="https://saktepe.com/blog/7-years-flutter-production-apps#3-state-management-starts-before-choosing-a-package" class="hash-link" aria-label="Direct link to 3. State management starts before choosing a package" title="Direct link to 3. State management starts before choosing a package" translate="no">​</a></h2>
<p>Flutter developers love talking about state management packages. Bloc, Cubit, Riverpod, Provider, Stacked, and many others can all be good choices in the right context.</p>
<p>But the package is rarely the real architecture.</p>
<p>The harder questions are usually about state ownership and lifecycle:</p>
<ul>
<li class="">Who owns this state?</li>
<li class="">When is it created?</li>
<li class="">When is it disposed?</li>
<li class="">Is this state local to a widget, shared across a feature, or global to the app?</li>
<li class="">Is the data fresh, cached, stale, loading, failed, or being retried?</li>
<li class="">Can two user actions race each other?</li>
<li class="">What happens when the user leaves the screen and comes back?</li>
</ul>
<p>In small examples, state is often shown as <code>loading</code>, <code>success</code>, and <code>error</code>. That is a good start, but production apps tend to need a richer vocabulary.</p>
<p>Sometimes the app has cached data and a background refresh. Sometimes the UI should keep old data visible while a retry happens. Sometimes the user performs an optimistic action before the backend confirms it. Sometimes a WebSocket event updates the same data that a REST endpoint just returned.</p>
<p>Those situations are not solved by picking a package. They are solved by designing the lifecycle of state carefully.</p>
<p>A good state management tool should make that design easier to express. It should not become the place where every unrelated responsibility goes to hide.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-performance-problems-are-usually-designed-in-slowly">4. Performance problems are usually designed in slowly<a href="https://saktepe.com/blog/7-years-flutter-production-apps#4-performance-problems-are-usually-designed-in-slowly" class="hash-link" aria-label="Direct link to 4. Performance problems are usually designed in slowly" title="Direct link to 4. Performance problems are usually designed in slowly" translate="no">​</a></h2>
<p>Performance issues often look sudden when users report them, but they usually arrive slowly.</p>
<p>One unnecessary rebuild is not dramatic. One heavy widget in a list might be fine. One image that is not sized well may not matter. One native ad integration with an awkward lifecycle can be acceptable. But production apps are made of accumulations.</p>
<p>Content-heavy applications taught me to respect small costs:</p>
<ul>
<li class="">widgets rebuilding more often than they need to</li>
<li class="">lists doing too much work during scroll</li>
<li class="">screens waiting for sequential network calls</li>
<li class="">images loading without a clear strategy</li>
<li class="">analytics, ads, and SDK callbacks adding hidden work</li>
<li class="">UI decisions that are fine on a flagship device but painful on older ones</li>
</ul>
<p>Performance is not only an optimization sprint at the end. It is the result of everyday decisions.</p>
<p>That does not mean every widget needs to be micro-optimized. It means the team should understand what kind of work is happening during build, layout, paint, navigation, and data loading. It means expensive operations should have a reason. It means the app should be tested on devices and conditions closer to the users who actually run it.</p>
<p>"It is fast on my device" is not a production performance strategy.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-production-needs-observability-not-guesses">5. Production needs observability, not guesses<a href="https://saktepe.com/blog/7-years-flutter-production-apps#5-production-needs-observability-not-guesses" class="hash-link" aria-label="Direct link to 5. Production needs observability, not guesses" title="Direct link to 5. Production needs observability, not guesses" translate="no">​</a></h2>
<p>Local testing is necessary, but it is not enough. Production has more devices, more networks, more app versions, more user behavior, and more strange timing than any development environment.</p>
<p>Crash reporting and analytics are not just tools you add before release. They are feedback systems.</p>
<p>A stack trace tells part of the story. The better questions are often around context:</p>
<ul>
<li class="">Which app version is affected?</li>
<li class="">Which screen was the user on?</li>
<li class="">Did this happen after a cold start, a deep link, or a background resume?</li>
<li class="">Is it tied to a specific device, OS version, network condition, or SDK?</li>
<li class="">Is this crash rare, or is it quietly hurting a core flow every day?</li>
</ul>
<p>Good observability changes the way you work. It turns production from a black box into a conversation. It helps you prioritize the bugs that matter. It also keeps engineering honest, because real user behavior often disagrees with our assumptions.</p>
<p>The point is not to collect endless dashboards. The point is to know enough to act.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-native-integrations-are-part-of-real-flutter-work">6. Native integrations are part of real Flutter work<a href="https://saktepe.com/blog/7-years-flutter-production-apps#6-native-integrations-are-part-of-real-flutter-work" class="hash-link" aria-label="Direct link to 6. Native integrations are part of real Flutter work" title="Direct link to 6. Native integrations are part of real Flutter work" translate="no">​</a></h2>
<p>Flutter is cross-platform, but production is still platform-specific.</p>
<p>Most apps eventually touch something native: permissions, push notifications, background tasks, home screen widgets, deep links, payments, ads, maps, camera, media, analytics SDKs, or platform-specific lifecycle behavior.</p>
<p>That is not a failure of Flutter. It is the reality of mobile development.</p>
<p>Working on Flutter plugins and native integrations taught me that platform channels are not something to fear. They are a boundary. Like every boundary, they need design.</p>
<p>The hard part is not always calling native code from Dart. The hard part is understanding lifecycle, threading, error handling, version differences, permissions, and how the native SDK behaves when the app is paused, resumed, killed, restored, or updated.</p>
<p>The best Flutter integrations feel boring from the Dart side. They expose a clear API, hide platform complexity, fail predictably, and document the cases where Android and iOS behave differently.</p>
<p>If a Flutter developer can cross that boundary with confidence, they become much more useful in real product work.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-maintainability-is-what-keeps-product-speed-alive">7. Maintainability is what keeps product speed alive<a href="https://saktepe.com/blog/7-years-flutter-production-apps#7-maintainability-is-what-keeps-product-speed-alive" class="hash-link" aria-label="Direct link to 7. Maintainability is what keeps product speed alive" title="Direct link to 7. Maintainability is what keeps product speed alive" translate="no">​</a></h2>
<p>Maintainability can sound like an internal engineering preference. In production, it becomes a product feature.</p>
<p>A maintainable codebase lets the product team move faster because changes are less risky. It lets bugs be fixed without rewriting unrelated flows. It helps new developers become useful sooner. It makes refactoring possible before the codebase turns into something everyone is afraid to touch.</p>
<p>For me, maintainability usually comes from simple things done consistently:</p>
<ul>
<li class="">clear names</li>
<li class="">small classes with honest responsibilities</li>
<li class="">feature boundaries that are easy to recognize</li>
<li class="">data models that do not leak everywhere</li>
<li class="">UI code that does not secretly contain business rules</li>
<li class="">abstractions that remove real duplication instead of hiding simple code</li>
<li class="">tests around behavior that would be expensive to break</li>
</ul>
<p>The goal is not to make the code perfect. The goal is to make it understandable enough that the next correct change is easier than the next messy one.</p>
<p>That is the kind of codebase I want to work in. More importantly, it is the kind of codebase that keeps a product moving after the first release.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-lesson-i-keep-coming-back-to">The lesson I keep coming back to<a href="https://saktepe.com/blog/7-years-flutter-production-apps#the-lesson-i-keep-coming-back-to" class="hash-link" aria-label="Direct link to The lesson I keep coming back to" title="Direct link to The lesson I keep coming back to" translate="no">​</a></h2>
<p>Flutter is still one of the best tools I know for building mobile products quickly. But after years of production work, I trust the boring parts more than the flashy parts.</p>
<p>Clear boundaries. Predictable state. Measured performance. Observable failures. Native integration when the product needs it. Code that explains where change should happen next.</p>
<p>That is what production keeps teaching me.</p>
<p>Fast development gets the app into the world. Sustainable engineering keeps it there.</p>]]></content:encoded>
            <category>Flutter</category>
            <category>Architecture</category>
            <category>Production</category>
            <category>Performance</category>
        </item>
        <item>
            <title><![CDATA[Understanding Flutter's Framework: Key, Context, and Lifecycle]]></title>
            <link>https://saktepe.com/blog/flutter-framework-must-know</link>
            <guid>https://saktepe.com/blog/flutter-framework-must-know</guid>
            <pubDate>Mon, 21 Jul 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[A practical guide to key, widget, element, render object, context, and lifecycle concepts in Flutter.]]></description>
            <content:encoded><![CDATA[<p>Writing good Flutter screens is not only about knowing <code>Column</code>, <code>Row</code>, and a handful of packages. Once you understand how the framework thinks, bugs become easier to reason about and your UI code becomes more predictable over time.</p>
<p>This is a short guide for refreshing the basics when needed, and for helping junior developers understand why these concepts exist in the first place.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="start-with-the-big-picture">Start with the big picture<a href="https://saktepe.com/blog/flutter-framework-must-know#start-with-the-big-picture" class="hash-link" aria-label="Direct link to Start with the big picture" title="Direct link to Start with the big picture" translate="no">​</a></h2>
<p>Flutter manages UI through three related trees:</p>
<ul>
<li class=""><strong>Widget tree:</strong> The immutable configuration tree that describes what the UI should look like.</li>
<li class=""><strong>Element tree:</strong> The living representation of widgets in the tree. State, context, and matching behavior become meaningful here.</li>
<li class=""><strong>Render object tree:</strong> The lower-level tree responsible for layout, paint, and hit testing.</li>
</ul>
<p>Most day-to-day Flutter code is widget code, but the framework keeps the app alive through elements and render objects. Understanding that separation is the foundation for understanding <code>Key</code>, <code>BuildContext</code>, and lifecycle methods.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-a-widget">What is a widget?<a href="https://saktepe.com/blog/flutter-framework-must-know#what-is-a-widget" class="hash-link" aria-label="Direct link to What is a widget?" title="Direct link to What is a widget?" translate="no">​</a></h2>
<p>A widget is not the thing drawn on the screen. A widget is a description.</p>
<p>For example, <code>Text('Hello')</code> is an immutable configuration that says, "show this text here." When a parent widget rebuilds, new widget objects may be created. That is normal, and it is part of Flutter's model.</p>
<p>That is why this sentence matters:</p>
<blockquote>
<p>Widgets can be recreated often; what matters is how the framework matches them to the living structure underneath.</p>
</blockquote>
<p><code>StatelessWidget</code> describes UI from the data it receives. <code>StatefulWidget</code> delegates mutable data to a separate <code>State</code> object. The <code>StatefulWidget</code> itself is still immutable; the part that changes is the <code>State</code> attached to it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-an-element">What is an element?<a href="https://saktepe.com/blog/flutter-framework-must-know#what-is-an-element" class="hash-link" aria-label="Direct link to What is an element?" title="Direct link to What is an element?" translate="no">​</a></h2>
<p>An element is the live representation of a widget in the tree. When a widget is placed into the UI, Flutter creates an element for it. <code>BuildContext</code> is essentially the safer public interface to that element.</p>
<p>An element is responsible for a few important things:</p>
<ul>
<li class="">It attaches a widget to a position in the tree.</li>
<li class="">It holds the <code>State</code> object for a <code>StatefulWidget</code>.</li>
<li class="">It manages parent and child relationships.</li>
<li class="">It decides whether the existing structure can be updated when a new widget arrives.</li>
</ul>
<p>During a rebuild, Flutter does not recreate everything from scratch. It compares the new widget tree with the existing element tree. If a widget at the same position has the same runtime type and key, the existing element can be updated. Otherwise, the old element is removed and a new one is created.</p>
<p>This is exactly where <code>Key</code> becomes important.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-a-render-object">What is a render object?<a href="https://saktepe.com/blog/flutter-framework-must-know#what-is-a-render-object" class="hash-link" aria-label="Direct link to What is a render object?" title="Direct link to What is a render object?" translate="no">​</a></h2>
<p>A render object handles the lower-level rendering work. <code>RenderObject</code>s calculate layout, paint pixels, and participate in hit testing.</p>
<p>Not every widget directly creates a render object. <code>StatelessWidget</code> and <code>StatefulWidget</code> are mostly composition tools. But many widgets, such as <code>Padding</code>, <code>Align</code>, <code>Text</code>, <code>DecoratedBox</code>, and <code>Flex</code>, eventually map to render object behavior for measuring and drawing.</p>
<p>In short:</p>
<ul>
<li class="">Widget: "What do I want?"</li>
<li class="">Element: "Where does this request live in the tree?"</li>
<li class="">Render object: "How do I measure and draw it?"</li>
</ul>
<p>This separation is one of the reasons Flutter can stay both declarative and fast.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-a-key-and-why-do-we-use-it">What is a key, and why do we use it?<a href="https://saktepe.com/blog/flutter-framework-must-know#what-is-a-key-and-why-do-we-use-it" class="hash-link" aria-label="Direct link to What is a key, and why do we use it?" title="Direct link to What is a key, and why do we use it?" translate="no">​</a></h2>
<p>A <code>Key</code> is a way to tell Flutter, "this widget has this identity." It is most useful in lists, reorderable UI, animations, and places where widgets of the same type can move around.</p>
<p>By default, Flutter matches widgets by position and type. For simple screens, that is enough. But when several children have the same type and their order changes, the framework may not know which element belongs to which piece of data. If those children hold state, that can become a visible bug.</p>
<div class="language-dart codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dart codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token class-name">ListView</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  children</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> todos</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">map</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">todo</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token keyword" style="color:#00009f">return</span><span class="token plain"> </span><span class="token class-name">TodoTile</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      key</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token class-name">ValueKey</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">todo</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">id</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      todo</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> todo</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">toList</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><br></div></code></pre></div></div>
<p>Here, <code>ValueKey(todo.id)</code> says, "this tile's identity is the todo id, not its current position in the list." If the list is reordered, the correct state stays with the correct item.</p>
<p>The key types you will usually reach for are:</p>
<ul>
<li class=""><code>ValueKey</code>: For stable values such as strings, integers, or database ids.</li>
<li class=""><code>ObjectKey</code>: For using an object itself as the identity.</li>
<li class=""><code>UniqueKey</code>: For creating a fresh unique identity. Use it carefully; creating a new <code>UniqueKey</code> on every build can force widgets to reset.</li>
<li class=""><code>GlobalKey</code>: For a tree-wide identity, direct state access, form validation, or a few special framework-level cases.</li>
</ul>
<p><code>GlobalKey</code> is powerful, but it is also heavier than local keys and easy to overuse. For ordinary list items, preserving state, or reorderable children, start by thinking about <code>ValueKey</code>.</p>
<p>The rule is simple: do not sprinkle keys everywhere. Use a key when the identity of a widget matters independently from its position.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-are-lifecycle-methods-for">What are lifecycle methods for?<a href="https://saktepe.com/blog/flutter-framework-must-know#what-are-lifecycle-methods-for" class="hash-link" aria-label="Direct link to What are lifecycle methods for?" title="Direct link to What are lifecycle methods for?" translate="no">​</a></h2>
<p>When you write a <code>StatefulWidget</code>, the living part is the <code>State</code> class. Lifecycle methods let you respond to the moments when that state is created, updated, and removed.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="initstate">initState<a href="https://saktepe.com/blog/flutter-framework-must-know#initstate" class="hash-link" aria-label="Direct link to initState" title="Direct link to initState" translate="no">​</a></h3>
<p><code>initState</code> runs once when the <code>State</code> object is first created.</p>
<p>Good uses for <code>initState</code> include:</p>
<ul>
<li class="">Creating an <code>AnimationController</code>, <code>TextEditingController</code>, or <code>FocusNode</code>.</li>
<li class="">Starting a stream, notifier, or controller subscription.</li>
<li class="">Preparing initial values.</li>
<li class="">Triggering work that should happen once when the screen opens.</li>
</ul>
<div class="language-dart codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dart codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token metadata function" style="color:#d73a49">@override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">void</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">initState</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">super</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">initState</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  _controller </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token class-name">AnimationController</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">vsync</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">this</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  _subscription </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> widget</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">userStream</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">listen</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">_onUserChanged</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>There is a <code>context</code> in <code>initState</code>, but this is not the right place to subscribe to inherited dependencies. If you need values such as <code>Theme</code>, <code>Localizations</code>, <code>MediaQuery</code>, or a provider that should update this state when it changes, use <code>didChangeDependencies</code>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="didchangedependencies">didChangeDependencies<a href="https://saktepe.com/blog/flutter-framework-must-know#didchangedependencies" class="hash-link" aria-label="Direct link to didChangeDependencies" title="Direct link to didChangeDependencies" translate="no">​</a></h3>
<p><code>didChangeDependencies</code> runs right after <code>initState</code>. It also runs again when an inherited dependency used by this state changes.</p>
<p>Common examples include:</p>
<ul>
<li class=""><code>Theme</code></li>
<li class=""><code>MediaQuery</code></li>
<li class=""><code>Localizations</code></li>
<li class=""><code>InheritedWidget</code></li>
<li class="">Tools built on inherited widgets, such as <code>Provider</code> or <code>BlocProvider</code></li>
</ul>
<p>If you read something through <code>context</code> and your state needs to react when that value changes, this method is often the right place.</p>
<div class="language-dart codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dart codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token metadata function" style="color:#d73a49">@override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">void</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">didChangeDependencies</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">super</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">didChangeDependencies</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">final</span><span class="token plain"> locale </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token class-name">Localizations</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">localeOf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">context</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">_loadedLocale </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> locale</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    _loadedLocale </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> locale</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token function" style="color:#d73a49">_loadLocalizedContent</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">locale</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>This method can run more than once, so avoid placing expensive work here without a guard.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="didupdatewidget">didUpdateWidget<a href="https://saktepe.com/blog/flutter-framework-must-know#didupdatewidget" class="hash-link" aria-label="Direct link to didUpdateWidget" title="Direct link to didUpdateWidget" translate="no">​</a></h3>
<p>When a parent rebuilds, the same <code>State</code> object may be kept, but the widget configuration attached to it can change. <code>didUpdateWidget</code> lets you compare the old widget with the new one.</p>
<p>A common use case is replacing a subscription when a dependency coming from the widget changes.</p>
<div class="language-dart codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dart codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token metadata function" style="color:#d73a49">@override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">void</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">didUpdateWidget</span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">covariant</span><span class="token plain"> </span><span class="token class-name">UserPanel</span><span class="token plain"> oldWidget</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">super</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">didUpdateWidget</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">oldWidget</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">oldWidget</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">userStream </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> widget</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">userStream</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    _subscription</span><span class="token operator" style="color:#393A34">?</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">cancel</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    _subscription </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> widget</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">userStream</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">listen</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">_onUserChanged</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>The important point is this: <code>initState</code> runs only once. If the parent sends new parameters and your state must react to them, handle that in <code>didUpdateWidget</code>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="dispose">dispose<a href="https://saktepe.com/blog/flutter-framework-must-know#dispose" class="hash-link" aria-label="Direct link to dispose" title="Direct link to dispose" translate="no">​</a></h3>
<p><code>dispose</code> runs when the <code>State</code> is permanently removed from the tree. It is the cleanup method.</p>
<p>This is where you close things such as:</p>
<ul>
<li class=""><code>AnimationController</code></li>
<li class=""><code>TextEditingController</code></li>
<li class=""><code>FocusNode</code></li>
<li class=""><code>ScrollController</code></li>
<li class=""><code>StreamSubscription</code></li>
<li class=""><code>Timer</code></li>
<li class="">Listeners you registered manually</li>
</ul>
<div class="language-dart codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dart codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token metadata function" style="color:#d73a49">@override</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">void</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">dispose</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  _subscription</span><span class="token operator" style="color:#393A34">?</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">cancel</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  _controller</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">dispose</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">super</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">dispose</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>Do not call <code>setState</code> inside <code>dispose</code>. At that point, the state is at the end of its lifecycle.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-buildcontext">What is BuildContext?<a href="https://saktepe.com/blog/flutter-framework-must-know#what-is-buildcontext" class="hash-link" aria-label="Direct link to What is BuildContext?" title="Direct link to What is BuildContext?" translate="no">​</a></h2>
<p><code>BuildContext</code> represents a widget's location in the tree. More technically, it is the public interface to an element.</p>
<p>Context lets Flutter walk up the tree and find the information you are asking for:</p>
<div class="language-dart codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dart codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">final</span><span class="token plain"> theme </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token class-name">Theme</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">of</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">context</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">final</span><span class="token plain"> size </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token class-name">MediaQuery</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">sizeOf</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">context</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token class-name">Navigator</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">of</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">context</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">push</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">.</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><br></div></code></pre></div></div>
<p>In those examples, <code>context</code> means, "I am here in the tree; find the nearest <code>Theme</code>, <code>MediaQuery</code>, or <code>Navigator</code> above me."</p>
<p>Common mistakes with context include:</p>
<ul>
<li class="">Storing context for a long time.</li>
<li class="">Using context after an async operation without checking whether the widget is still mounted.</li>
<li class="">Trying to read a provider, navigator, or scaffold from a context that is not below it in the tree yet.</li>
</ul>
<p>After async work, <code>mounted</code> matters:</p>
<div class="language-dart codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dart codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token class-name">Future</span><span class="token generics punctuation" style="color:#393A34">&lt;</span><span class="token generics keyword" style="color:#00009f">void</span><span class="token generics punctuation" style="color:#393A34">&gt;</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">save</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">async</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">await</span><span class="token plain"> repository</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">save</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token operator" style="color:#393A34">!</span><span class="token plain">mounted</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token keyword" style="color:#00009f">return</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token class-name">Navigator</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">of</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">context</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">pop</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">;</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>Also remember that context is location-based. If you create a provider and try to read it using a context from above that provider in the same build method, it will not work. Use a <code>Builder</code> when you need a lower context.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-practical-checklist">A practical checklist<a href="https://saktepe.com/blog/flutter-framework-must-know#a-practical-checklist" class="hash-link" aria-label="Direct link to A practical checklist" title="Direct link to A practical checklist" translate="no">​</a></h2>
<p>When writing a Flutter screen, it helps to ask:</p>
<ul>
<li class="">Does this widget really need state, or can it render from the data it receives?</li>
<li class="">Do stateful list items need a stable key?</li>
<li class="">Did I dispose the controller, listener, or subscription I created?</li>
<li class="">Does this state need to react when widget parameters change?</li>
<li class="">Is the value I read from <code>context</code> an inherited dependency?</li>
<li class="">Do I need a <code>mounted</code> check after async work?</li>
<li class="">Is <code>build</code> only describing UI, or is it causing side effects?</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="final-thought">Final thought<a href="https://saktepe.com/blog/flutter-framework-must-know#final-thought" class="hash-link" aria-label="Direct link to Final thought" title="Direct link to Final thought" translate="no">​</a></h2>
<p>Becoming strong with Flutter is less about memorizing every widget and more about understanding the framework's mental model.</p>
<p>Widgets are descriptions. Elements are the living representation of those descriptions. Render objects measure and draw. Keys help preserve the right identity. Lifecycle methods tell you when to prepare, update, and clean up state. Context lets you talk to the framework from a specific location in the tree.</p>
<p>Once these ideas click, Flutter code feels less magical and much easier to reason about. That is one of the most important steps from junior implementation toward senior judgment.</p>]]></content:encoded>
            <category>Flutter</category>
            <category>Architecture</category>
        </item>
        <item>
            <title><![CDATA[Server-Side Preparation for Deep Linking]]></title>
            <link>https://saktepe.com/blog/deeplinking-server-side-prep</link>
            <guid>https://saktepe.com/blog/deeplinking-server-side-prep</guid>
            <pubDate>Sat, 15 Mar 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[A practical guide to publishing apple-app-site-association and assetlinks.json correctly for iOS Universal Links and Android App Links.]]></description>
            <content:encoded><![CDATA[<p>Deep linking is usually explained through routing code on the mobile side. That part matters, but in the world of verified links there is another quiet, critical piece: the server-side preparation.</p>
<p>iOS Universal Links and Android App Links want to establish a secure association between a domain and an app. That association is not verified from inside the app — it is verified through small JSON files published under the domain. If the file sits at the wrong path, gets redirected, has a wrong MIME type, or is not publicly reachable, the link may never make it into the app, no matter how correct your mobile code is.</p>
<!-- -->
<p>This post focuses only on the server-side work:</p>
<ul>
<li class=""><code>apple-app-site-association</code> for iOS</li>
<li class=""><code>assetlinks.json</code> for Android</li>
<li class="">The correct <code>.well-known</code> path</li>
<li class="">MIME type, redirect, and access checks</li>
<li class="">Quick verification commands to run after deploy</li>
</ul>
<p>The examples use placeholder values like <code>example.com</code> and <code>com.example.app</code>. You should substitute your own domain, bundle id, package name, and signing details.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-we-publish-files-on-the-server">Why we publish files on the server<a href="https://saktepe.com/blog/deeplinking-server-side-prep#why-we-publish-files-on-the-server" class="hash-link" aria-label="Direct link to Why we publish files on the server" title="Direct link to Why we publish files on the server" translate="no">​</a></h2>
<p>The core idea of verified deep linking is simple: "Does this domain actually grant this app permission to open its links?"</p>
<p>On iOS, that verification is done through the <code>apple-app-site-association</code> file. On Android, the equivalent is <code>assetlinks.json</code>. The operating system downloads these files from a known location under the domain, compares their contents with the app's own identity, and — if everything matches — can route links directly into the app.</p>
<p>This is why server preparation deserves more than a "just upload a JSON file" mindset. A small HTTP detail here can silently break the entire deep linking flow.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="general-folder-layout">General folder layout<a href="https://saktepe.com/blog/deeplinking-server-side-prep#general-folder-layout" class="hash-link" aria-label="Direct link to General folder layout" title="Direct link to General folder layout" translate="no">​</a></h2>
<p>For both platforms, the files go under the <code>.well-known</code> directory at the root of the domain:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">https://example.com/.well-known/apple-app-site-association</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">https://example.com/.well-known/assetlinks.json</span><br></div></code></pre></div></div>
<p>A few rules apply:</p>
<ul>
<li class="">The <code>.well-known</code> directory must be publicly accessible.</li>
<li class="">The files must not sit behind authentication.</li>
<li class="">The endpoints must return <code>200 OK</code>.</li>
<li class="">There must be no <code>301</code> or <code>302</code> redirects.</li>
<li class="">The response body must be JSON, not HTML.</li>
<li class="">CDN, WAF, bot protection, or geo-blocking must not interfere with these files.</li>
</ul>
<p>If the app should open links for both <code>example.com</code> and <code>www.example.com</code>, serve the files on both hosts at the correct path. Relying on a redirect from the root domain to <code>www</code> is not a reliable strategy for verification.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ios-apple-app-site-association">iOS: apple-app-site-association<a href="https://saktepe.com/blog/deeplinking-server-side-prep#ios-apple-app-site-association" class="hash-link" aria-label="Direct link to iOS: apple-app-site-association" title="Direct link to iOS: apple-app-site-association" translate="no">​</a></h2>
<p>The file name on the iOS side must be exactly:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">apple-app-site-association</span><br></div></code></pre></div></div>
<p>The file must not have a <code>.json</code> extension. Locally, or while preparing the file, you can keep it as <code>apple-app-site-association.json</code>, but by the time it reaches the server the extension must be dropped.</p>
<p>The file must be reachable at this exact URL:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">https://example.com/.well-known/apple-app-site-association</span><br></div></code></pre></div></div>
<p>An example payload:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token property" style="color:#36acaa">"applinks"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"apps"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"details"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">"appID"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"ABCDE12345.com.example.app"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token property" style="color:#36acaa">"paths"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token string" style="color:#e3116c">"*"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>Here <code>appID</code> is the combination of the Apple Team ID (or App ID Prefix) and the iOS bundle identifier:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">&lt;TeamID&gt;.&lt;BundleID&gt;</span><br></div></code></pre></div></div>
<p>In the example:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">ABCDE12345.com.example.app</span><br></div></code></pre></div></div>
<p>The <code>paths</code> field specifies which URL paths the app is allowed to open. <code>["*"]</code> covers everything. For a tighter setup, it is often cleaner to enumerate only the paths you actually need:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token property" style="color:#36acaa">"paths"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string" style="color:#e3116c">"/articles/*"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token string" style="color:#e3116c">"/news/*"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">]</span><br></div></code></pre></div></div>
<p>Apple's more recent documentation uses the <code>appIDs</code> and <code>components</code> format. The <code>appID</code>/<code>paths</code> format shown above is the legacy format and is still common in practice. The important thing is to not mix the two formats within the same file.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="server-configuration-for-aasa">Server configuration for AASA<a href="https://saktepe.com/blog/deeplinking-server-side-prep#server-configuration-for-aasa" class="hash-link" aria-label="Direct link to Server configuration for AASA" title="Direct link to Server configuration for AASA" translate="no">​</a></h2>
<p>Because the AASA file has no extension, some web servers will serve it as <code>application/octet-stream</code> or try to force it as a download. The behavior we actually want:</p>
<ul>
<li class="">Opening the URL in a browser shows the file.</li>
<li class="">The file is not forced as a download.</li>
<li class=""><code>Content-Type</code> is <code>application/json</code>.</li>
<li class="">The response is <code>200 OK</code>.</li>
<li class="">No redirects.</li>
</ul>
<p>Nginx example:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">location = /.well-known/apple-app-site-association {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    default_type application/json;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    try_files $uri =404;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>For Apache, using <code>ForceType</code> on the extensionless AASA file is the safer choice:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">&lt;Files "apple-app-site-association"&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    ForceType application/json</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">&lt;/Files&gt;</span><br></div></code></pre></div></div>
<p>If your hosting layer exposes metadata — for example S3 or a similar object storage — set the <code>Content-Type</code> metadata on the file to <code>application/json</code>. And if you have an SPA fallback rule, make sure this path does not fall through to <code>index.html</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="android-assetlinksjson">Android: assetlinks.json<a href="https://saktepe.com/blog/deeplinking-server-side-prep#android-assetlinksjson" class="hash-link" aria-label="Direct link to Android: assetlinks.json" title="Direct link to Android: assetlinks.json" translate="no">​</a></h2>
<p>On Android, the file is served with its extension:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">assetlinks.json</span><br></div></code></pre></div></div>
<p>Exact URL:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">https://example.com/.well-known/assetlinks.json</span><br></div></code></pre></div></div>
<p>An example payload:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"relation"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token string" style="color:#e3116c">"delegate_permission/common.handle_all_urls"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"target"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"namespace"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"android_app"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"package_name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"com.example.app"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token property" style="color:#36acaa">"sha256_cert_fingerprints"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token string" style="color:#e3116c">"AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">]</span><br></div></code></pre></div></div>
<p>The fields:</p>
<ul>
<li class=""><code>relation</code>: declares that the app is allowed to handle URLs for this domain.</li>
<li class=""><code>namespace</code>: <code>android_app</code> for Android app association.</li>
<li class=""><code>package_name</code>: the Android application's package name.</li>
<li class=""><code>sha256_cert_fingerprints</code>: the SHA-256 fingerprint of the certificate that signs the app.</li>
</ul>
<p>The SHA-256 fingerprint is easy to get wrong. If you use Play App Signing, you usually need the app signing certificate fingerprint shown in the Play Console — not the upload key. Debug builds have their own debug keystore fingerprint, which is different again. Rather than mixing test certificates into your production domain, it is cleaner to use a dedicated flavor or a separate test domain.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="server-configuration-for-assetlinksjson">Server configuration for assetlinks.json<a href="https://saktepe.com/blog/deeplinking-server-side-prep#server-configuration-for-assetlinksjson" class="hash-link" aria-label="Direct link to Server configuration for assetlinks.json" title="Direct link to Server configuration for assetlinks.json" translate="no">​</a></h2>
<p>Because the Android file has a <code>.json</code> extension, most servers will serve it with the right MIME type out of the box. Still, the same checks apply as on iOS:</p>
<ul>
<li class=""><code>Content-Type</code> is <code>application/json</code>.</li>
<li class="">The file is reachable over HTTPS.</li>
<li class="">No redirects.</li>
<li class="">No authentication.</li>
<li class="">The JSON is valid.</li>
</ul>
<p>For Apache, if you want to make the <code>.json</code> MIME type explicit:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">AddType application/json .json</span><br></div></code></pre></div></div>
<p>For Nginx, the default <code>mime.types</code> usually covers JSON. If you prefer a dedicated location block:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">location = /.well-known/assetlinks.json {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    default_type application/json;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    try_files $uri =404;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="post-deploy-verification">Post-deploy verification<a href="https://saktepe.com/blog/deeplinking-server-side-prep#post-deploy-verification" class="hash-link" aria-label="Direct link to Post-deploy verification" title="Direct link to Post-deploy verification" translate="no">​</a></h2>
<p>Once the files are uploaded, the first check can be in the browser — but the real check is inspecting the HTTP response from the terminal.</p>
<p>iOS:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">curl -i https://example.com/.well-known/apple-app-site-association</span><br></div></code></pre></div></div>
<p>Android:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">curl -i https://example.com/.well-known/assetlinks.json</span><br></div></code></pre></div></div>
<p>What you want to see:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">HTTP/2 200</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">content-type: application/json</span><br></div></code></pre></div></div>
<p>What you do not want to see:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">HTTP/2 301</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">HTTP/2 302</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">content-type: text/html</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">content-disposition: attachment</span><br></div></code></pre></div></div>
<p>To additionally confirm the JSON is valid:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">curl -fsS https://example.com/.well-known/assetlinks.json | jq .</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">curl -fsS https://example.com/.well-known/apple-app-site-association | jq .</span><br></div></code></pre></div></div>
<p>Note that I deliberately do not use <code>-L</code>. If there is a redirect, I want to catch it, not hide it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="most-common-mistakes">Most common mistakes<a href="https://saktepe.com/blog/deeplinking-server-side-prep#most-common-mistakes" class="hash-link" aria-label="Direct link to Most common mistakes" title="Direct link to Most common mistakes" translate="no">​</a></h2>
<p>Most of the problems in this setup show up in the server response before they ever reach the app code:</p>
<ul>
<li class="">Uploading the file as <code>apple-app-site-association.json</code> instead of the extensionless name.</li>
<li class="">Serving the AASA file from somewhere other than <code>.well-known</code>.</li>
<li class="">Letting <code>/.well-known/*</code> fall through to an SPA <code>index.html</code>.</li>
<li class="">Redirecting HTTP to HTTPS, or the root domain to <code>www</code>, on these endpoints.</li>
<li class="">Putting the file behind basic auth, VPN, WAF, or bot protection.</li>
<li class="">Using the wrong SHA-256 fingerprint on Android.</li>
<li class="">Using the wrong Team ID or bundle identifier on iOS.</li>
<li class="">Mixing comments, trailing commas, or an HTML body into the JSON.</li>
<li class="">Expecting changes to propagate instantly without clearing CDN caches.</li>
</ul>
<p>Caching is another practical detail. On Android, the device may need a re-verification trigger. On iOS, Associated Domains verification goes through Apple's CDN, so do not expect updates to hit every device immediately.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="short-release-checklist">Short release checklist<a href="https://saktepe.com/blog/deeplinking-server-side-prep#short-release-checklist" class="hash-link" aria-label="Direct link to Short release checklist" title="Direct link to Short release checklist" translate="no">​</a></h2>
<p>Before shipping, it is worth walking through this list once:</p>
<ul>
<li class="">Is <code>https://example.com/.well-known/apple-app-site-association</code> reachable?</li>
<li class="">Is the AASA file extensionless?</li>
<li class="">Is <code>https://example.com/.well-known/assetlinks.json</code> reachable?</li>
<li class="">Do both endpoints return <code>200 OK</code>?</li>
<li class="">Do both endpoints return <code>application/json</code>?</li>
<li class="">Are there any redirects?</li>
<li class="">Are the files reachable publicly, without authentication?</li>
<li class="">Is the iOS <code>appID</code> built from the correct Team ID and Bundle ID?</li>
<li class="">Is the Android <code>package_name</code> correct?</li>
<li class="">Does the Android SHA-256 fingerprint match the release signing certificate?</li>
<li class="">Are the files published for all variants of the domain?</li>
</ul>
<p>No matter how carefully the mobile side of deep linking is written, verified link behavior is not reliable until this checklist is done. The good news: once the server side is set up correctly, most of the uncertainty in deep link debugging disappears.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="references">References<a href="https://saktepe.com/blog/deeplinking-server-side-prep#references" class="hash-link" aria-label="Direct link to References" title="Direct link to References" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://developer.apple.com/documentation/Xcode/supporting-associated-domains" target="_blank" rel="noopener noreferrer" class="">Apple: Supporting associated domains</a></li>
<li class=""><a href="https://developer.apple.com/documentation/Technotes/tn3155-debugging-universal-links" target="_blank" rel="noopener noreferrer" class="">Apple: TN3155 Debugging universal links</a></li>
<li class=""><a href="https://developer.android.com/training/app-links/configure-assetlinks" target="_blank" rel="noopener noreferrer" class="">Android: Configure website associations</a></li>
<li class=""><a href="https://developer.android.com/training/app-links/troubleshoot" target="_blank" rel="noopener noreferrer" class="">Android: Troubleshoot App Links</a></li>
</ul>]]></content:encoded>
            <category>Deep Linking</category>
            <category>iOS</category>
            <category>Android</category>
            <category>Production</category>
        </item>
    </channel>
</rss>