<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Madhuri Latha Gondi</title>
    <description>The latest articles on DEV Community by Madhuri Latha Gondi (@madhuri_lathagondi).</description>
    <link>https://dev.to/madhuri_lathagondi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3806861%2F9834f036-ed59-4b1f-9e1e-e7b35fa1c5bb.png</url>
      <title>DEV Community: Madhuri Latha Gondi</title>
      <link>https://dev.to/madhuri_lathagondi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/madhuri_lathagondi"/>
    <language>en</language>
    <item>
      <title>Building Dependency-Injection-Driven Navigation in Modular iOS Applications</title>
      <dc:creator>Madhuri Latha Gondi</dc:creator>
      <pubDate>Mon, 30 Mar 2026 23:57:13 +0000</pubDate>
      <link>https://dev.to/madhuri_lathagondi/building-dependency-injection-driven-navigation-in-modular-ios-applications-1jon</link>
      <guid>https://dev.to/madhuri_lathagondi/building-dependency-injection-driven-navigation-in-modular-ios-applications-1jon</guid>
      <description>&lt;p&gt;By Madhuri Latha Gondi | Mobile Architecture &amp;amp; iOS Platform Engineering&lt;/p&gt;

&lt;p&gt;Introduction&lt;/p&gt;

&lt;p&gt;As iOS applications scale, navigation quickly becomes one of the most complex architectural challenges. Large applications often contain multiple feature modules, deep linking entry points, and legacy integrations. Traditional navigation approaches — such as directly pushing view controllers — create tight coupling between modules and reduce scalability.&lt;br&gt;
Modern iOS architecture increasingly favors dependency injection and modular routing to improve flexibility, maintainability, and testability. This approach helps teams scale applications without introducing fragile navigation flows.&lt;br&gt;
In this article, we'll explore how to implement Dependency-Injection-Driven Navigation for modular iOS applications.&lt;/p&gt;

&lt;p&gt;The Problem with Traditional Navigation&lt;br&gt;
Most large iOS applications face these issues:&lt;br&gt;
• ViewControllers directly push other ViewControllers&lt;br&gt;
• Feature modules depend on each other&lt;br&gt;
• Deep linking becomes difficult&lt;br&gt;
• Testing navigation flows becomes complex&lt;br&gt;
• Legacy integration creates tight coupling&lt;br&gt;
As apps grow, these problems slow down development and introduce technical debt.&lt;/p&gt;

&lt;p&gt;The Modern Approach: Dependency-Injection-Driven Navigation&lt;br&gt;
Instead of modules navigating directly, we introduce:&lt;br&gt;
• Navigation Manager&lt;br&gt;
• Dependency Container&lt;br&gt;
• Deep Link Descriptor&lt;br&gt;
• Feature Module Provider&lt;br&gt;
This architecture allows modules to remain independent and loosely coupled.&lt;br&gt;
Modern routing approaches often define routes using enums or descriptors, while modules implement route handlers, coordinated through a router abstraction. This prevents modules from depending directly on each other.&lt;/p&gt;

&lt;p&gt;Feature Module&lt;br&gt;
      ↓&lt;br&gt;
DeepLink Descriptor&lt;br&gt;
      ↓&lt;br&gt;
Navigation Manager&lt;br&gt;
      ↓&lt;br&gt;
Dependency Container&lt;br&gt;
      ↓&lt;br&gt;
Feature Provider&lt;br&gt;
      ↓&lt;br&gt;
ViewController&lt;/p&gt;

&lt;p&gt;This separation improves:&lt;br&gt;
✔ Testability&lt;br&gt;
✔ Scalability&lt;br&gt;
✔ Maintainability&lt;br&gt;
✔ Modularization&lt;/p&gt;

&lt;p&gt;Step 1: Define Navigation Intent&lt;br&gt;
Instead of navigating directly, define a descriptor:&lt;/p&gt;

&lt;p&gt;struct DeepLinkDescriptor {&lt;br&gt;
    let route: String&lt;br&gt;
    let parameters: [String: String]&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This separates navigation intent from execution.&lt;/p&gt;

&lt;p&gt;Step 2: Navigation Manager&lt;/p&gt;

&lt;p&gt;protocol NavigationManager {&lt;br&gt;
    func navigate(_ descriptor: DeepLinkDescriptor)&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This centralizes navigation logic.&lt;/p&gt;

&lt;p&gt;Step 3: Dependency Injection Container&lt;/p&gt;

&lt;p&gt;protocol NavigationContainerProvider {&lt;br&gt;
    func resolveViewController(&lt;br&gt;
        descriptor: DeepLinkDescriptor&lt;br&gt;
    ) -&amp;gt; UIViewController?&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;This allows feature modules to remain independent.&lt;/p&gt;

&lt;p&gt;Step 4: Feature Module Implementation&lt;br&gt;
Each module registers its routes:&lt;br&gt;
class ShorexFeatureProvider {&lt;br&gt;
    func buildVC(&lt;br&gt;
        parameters: [String: String]&lt;br&gt;
    ) -&amp;gt; UIViewController {&lt;br&gt;
        return ShorexViewController()&lt;br&gt;
    }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Benefits of Dependency-Injection Navigation&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Modular Architecture
Feature modules become independent.&lt;/li&gt;
&lt;li&gt;Deep Link Support
Easy to handle multiple entry points.&lt;/li&gt;
&lt;li&gt;Testability
Navigation flows become testable.&lt;/li&gt;
&lt;li&gt;Scalability
Supports large enterprise apps.
Modern modular architecture improves team productivity and reduces dependencies between features.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When to Use This Architecture&lt;br&gt;
Use this approach when:&lt;br&gt;
• Large enterprise apps&lt;br&gt;
• Multiple teams working on features&lt;br&gt;
• Deep linking requirements&lt;br&gt;
• Legacy system integration&lt;br&gt;
• Modular architecture&lt;/p&gt;

&lt;p&gt;Real-World Example&lt;br&gt;
This architecture works well for:&lt;br&gt;
• Commerce apps&lt;br&gt;
• Banking apps&lt;br&gt;
• Travel apps&lt;br&gt;
• Healthcare apps&lt;br&gt;
These applications often require flexible navigation and modular design.&lt;/p&gt;

&lt;p&gt;Final Thoughts&lt;br&gt;
Dependency-Injection-Driven Navigation is a powerful approach for building scalable iOS applications. By separating navigation intent from execution, teams can build modular, testable, and maintainable systems.&lt;br&gt;
As mobile applications grow, adopting scalable navigation architectures becomes essential for long-term success.&lt;/p&gt;

&lt;p&gt;Author&lt;br&gt;
Madhuri Latha Gondi&lt;br&gt;
Senior iOS Mobile Engineering Consultant&lt;br&gt;
IEEE Senior Member | Mobile Architecture | Scalable Systems&lt;/p&gt;

</description>
      <category>deeplinking</category>
      <category>dependency</category>
      <category>ios</category>
      <category>modular</category>
    </item>
    <item>
      <title>Implementing Descriptor-Driven Navigation Architecture (DDNA) in Modular iOS Applications</title>
      <dc:creator>Madhuri Latha Gondi</dc:creator>
      <pubDate>Sun, 08 Mar 2026 05:06:19 +0000</pubDate>
      <link>https://dev.to/madhuri_lathagondi/implementing-descriptor-driven-navigation-architecture-ddna-in-modular-ios-applications-4n2h</link>
      <guid>https://dev.to/madhuri_lathagondi/implementing-descriptor-driven-navigation-architecture-ddna-in-modular-ios-applications-4n2h</guid>
      <description>&lt;p&gt;By Madhuri Latha Gondi | Mobile Architecture &amp;amp; iOS Platform Engineering&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F45ef1af2z7p569nlwfih.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F45ef1af2z7p569nlwfih.png" alt=" " width="800" height="1200"&gt;&lt;/a&gt;&lt;br&gt;
In modern iOS applications, navigation is no longer a simple transition between screens. Users can enter an app through multiple entry points such as push notifications, universal links, marketing campaigns, and in-app actions.&lt;/p&gt;

&lt;p&gt;As applications grow larger and more modular, navigation logic often becomes fragmented and difficult to maintain.&lt;/p&gt;

&lt;p&gt;In many codebases, navigation begins with something simple like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;navigationController?.pushViewController(DetailsViewController(), animated: true)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this works for small applications, large mobile systems quickly run into problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;tight coupling between feature modules&lt;/li&gt;
&lt;li&gt;inconsistent deep link handling&lt;/li&gt;
&lt;li&gt;navigation logic spread across many controllers&lt;/li&gt;
&lt;li&gt;poor testability&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To address these challenges, I use an architectural approach called &lt;strong&gt;Descriptor-Driven Navigation Architecture (DDNA)&lt;/strong&gt;.&lt;br&gt;
DDNA separates navigation intent from navigation implementation, allowing applications to scale navigation logic cleanly across modules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is Descriptor-Driven Navigation Architecture?&lt;/strong&gt;&lt;br&gt;
Instead of navigating directly to a screen, the application creates a navigation descriptor that represents the destination.&lt;br&gt;
Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let descriptor = DeepLinkDescriptor(
 path: "/profile/details",
 parameters: ["userId": "12345"],
 presentationMode: .push
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This descriptor is then processed through a routing pipeline.&lt;/p&gt;

&lt;p&gt;Entry Point&lt;br&gt;
(UI / Push Notification / Universal Link)&lt;br&gt;
  ↓&lt;br&gt;
DeepLinkDescriptor&lt;br&gt;
  ↓&lt;br&gt;
NavigationManager&lt;br&gt;
  ↓&lt;br&gt;
DeepLinkMapper&lt;br&gt;
  ↓&lt;br&gt;
Feature ViewController&lt;/p&gt;

&lt;p&gt;`This architecture makes navigation consistent across the application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — Define a Navigation Descriptor&lt;/strong&gt;&lt;br&gt;
The foundation of DDNA is the DeepLinkDescriptor object.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;br&gt;
struct DeepLinkDescriptor {&lt;/p&gt;

&lt;p&gt;enum PresentationMode {&lt;br&gt;
  case push&lt;br&gt;
  case modal&lt;br&gt;
 }&lt;/p&gt;

&lt;p&gt;let path: String&lt;br&gt;
 let parameters: [String: String?]&lt;br&gt;
 let presentationMode: PresentationMode&lt;br&gt;
}&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;br&gt;
This object represents navigation intent rather than a concrete screen.&lt;br&gt;
Because it is independent of UIKit, descriptors can be created from:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;UI actions&lt;/li&gt;
&lt;li&gt;push notifications&lt;/li&gt;
&lt;li&gt;universal links&lt;/li&gt;
&lt;li&gt;background events&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Implement a Navigation Manager&lt;/strong&gt;&lt;br&gt;
The NavigationManager coordinates routing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
protocol NavigationManaging {&lt;br&gt;
 func navigate(using descriptor: DeepLinkDescriptor)&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;br&gt;
Implementation:&lt;/p&gt;

&lt;p&gt;`final class NavigationManager: NavigationManaging {&lt;/p&gt;

&lt;p&gt;private let mapper: DeepLinkMapper&lt;br&gt;
 private weak var navigationContainer: NavigationContainer?&lt;/p&gt;

&lt;p&gt;init(&lt;br&gt;
  mapper: DeepLinkMapper,&lt;br&gt;
  navigationContainer: NavigationContainer&lt;br&gt;
 ) {&lt;br&gt;
  self.mapper = mapper&lt;br&gt;
  self.navigationContainer = navigationContainer&lt;br&gt;
 }&lt;/p&gt;

&lt;p&gt;func navigate(using descriptor: DeepLinkDescriptor) {&lt;/p&gt;

&lt;p&gt;guard let viewController = mapper.map(descriptor) else {&lt;br&gt;
   return&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;switch descriptor.presentationMode {&lt;/p&gt;

&lt;p&gt;case .push:&lt;br&gt;
   navigationContainer?.push(viewController)&lt;/p&gt;

&lt;p&gt;case .modal:&lt;br&gt;
   navigationContainer?.present(viewController)&lt;br&gt;
  }&lt;br&gt;
 }&lt;br&gt;
}`&lt;/p&gt;

&lt;p&gt;The NavigationManager orchestrates navigation without knowing the details of each feature module.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — Map Descriptors to Screens&lt;/strong&gt;&lt;br&gt;
The DeepLinkMapper converts descriptors into actual destinations.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
protocol DeepLinkMapper {&lt;br&gt;
 func map(_ descriptor: DeepLinkDescriptor) -&amp;gt; UIViewController?&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;br&gt;
Example implementation:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;br&gt;
final class FeatureDeepLinkMapper: DeepLinkMapper {&lt;/p&gt;

&lt;p&gt;func map(_ descriptor: DeepLinkDescriptor) -&amp;gt; UIViewController? {&lt;/p&gt;

&lt;p&gt;switch descriptor.path {&lt;/p&gt;

&lt;p&gt;case "/profile/details":&lt;/p&gt;

&lt;p&gt;let userId = descriptor.parameters["userId"] ?? nil&lt;/p&gt;

&lt;p&gt;return ProfileDetailsViewController(userId: userId)&lt;/p&gt;

&lt;p&gt;default:&lt;br&gt;
   return nil&lt;br&gt;
  }&lt;br&gt;
 }&lt;br&gt;
}&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;br&gt;
This isolates screen creation from navigation logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4 — Introduce a Navigation Container&lt;/strong&gt;&lt;br&gt;
Instead of depending directly on UINavigationController, DDNA introduces a container abstraction.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;br&gt;
protocol NavigationContainer: AnyObject {&lt;/p&gt;

&lt;p&gt;func push(_ viewController: UIViewController)&lt;/p&gt;

&lt;p&gt;func present(_ viewController: UIViewController)&lt;br&gt;
}&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;br&gt;
Example implementation:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;br&gt;
final class DefaultNavigationContainer: NavigationContainer {&lt;/p&gt;

&lt;p&gt;weak var navigationController: UINavigationController?&lt;/p&gt;

&lt;p&gt;func push(_ viewController: UIViewController) {&lt;br&gt;
  navigationController?.pushViewController(viewController, animated: true)&lt;br&gt;
 }&lt;/p&gt;

&lt;p&gt;func present(_ viewController: UIViewController) {&lt;br&gt;
  navigationController?.present(viewController, animated: true)&lt;br&gt;
 }&lt;br&gt;
}&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;br&gt;
This abstraction allows navigation to work with UIKit, SwiftUI bridges, or custom containers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5 — Assemble Using Dependency Injection&lt;/strong&gt;&lt;br&gt;
Instead of global singletons, dependencies are assembled at the application level.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;br&gt;
final class NavigationManagerFactory {&lt;/p&gt;

&lt;p&gt;static func make(&lt;br&gt;
  navigationController: UINavigationController&lt;br&gt;
 ) -&amp;gt; NavigationManager {&lt;/p&gt;

&lt;p&gt;let container = DefaultNavigationContainer()&lt;br&gt;
  container.navigationController = navigationController&lt;/p&gt;

&lt;p&gt;let mapper = FeatureDeepLinkMapper()&lt;/p&gt;

&lt;p&gt;return NavigationManager(&lt;br&gt;
   mapper: mapper,&lt;br&gt;
   navigationContainer: container&lt;br&gt;
  )&lt;br&gt;
 }&lt;br&gt;
}&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;br&gt;
Dependency injection improves modularity and testability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example Usage&lt;/strong&gt;&lt;br&gt;
A feature module can trigger navigation using a descriptor.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;br&gt;
let descriptor = DeepLinkDescriptor(&lt;br&gt;
 path: "/profile/details",&lt;br&gt;
 parameters: ["userId": "12345"],&lt;br&gt;
 presentationMode: .push&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;navigationManager.navigate(using: descriptor)&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;br&gt;
The feature does not need to know how the destination screen is constructed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supporting Universal Links&lt;/strong&gt;&lt;br&gt;
DDNA also simplifies deep link routing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;br&gt;
func descriptor(from url: URL) -&amp;gt; DeepLinkDescriptor {&lt;/p&gt;

&lt;p&gt;let components = URLComponents(url: url, resolvingAgainstBaseURL: false)&lt;/p&gt;

&lt;p&gt;var parameters: [String: String?] = [:]&lt;/p&gt;

&lt;p&gt;components?.queryItems?.forEach {&lt;br&gt;
  parameters[$0.name] = $0.value&lt;br&gt;
 }&lt;/p&gt;

&lt;p&gt;return DeepLinkDescriptor(&lt;br&gt;
  path: components?.path ?? "",&lt;br&gt;
  parameters: parameters,&lt;br&gt;
  presentationMode: .push&lt;br&gt;
 )&lt;br&gt;
}&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;br&gt;
Now UI actions, push notifications, and universal links share the same navigation flow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Benefits of DDNA&lt;/strong&gt;&lt;br&gt;
Descriptor-Driven Navigation Architecture provides several advantages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;modular feature isolation&lt;/li&gt;
&lt;li&gt;centralized navigation logic&lt;/li&gt;
&lt;li&gt;consistent deep linking&lt;/li&gt;
&lt;li&gt;improved testability&lt;/li&gt;
&lt;li&gt;scalable mobile architecture
This approach is particularly useful for enterprise mobile applications with multiple feature teams.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;br&gt;
Navigation often becomes one of the most complex parts of a mobile architecture as applications grow.&lt;br&gt;
By separating navigation intent from navigation execution, DDNA allows teams to build scalable routing systems while maintaining modular codebases.&lt;br&gt;
Treating navigation as a platform capability rather than a controller detail enables mobile systems to scale more effectively as products evolve.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>mobiledev</category>
      <category>deeplinking</category>
      <category>swift</category>
    </item>
  </channel>
</rss>
