DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Get Ready for Android 15: Must-Have Testing Strategies for Effective Updates
  • How To Add Audio Animations to Android Apps
  • 9 Tips for Building Apps to Withstand AI-Driven Bot Attacks
  • Pragmatic Paths to On-Device AI on Android with ML Kit

Trending

  • Smart Deployment Strategies for Modern Applications
  • OpenAPI From Code With Spring and Java: A Recipe for Your CI
  • Why AI-Generated Code Breaks Your Testing Assumptions
  • Why Pass/Fail CI Pipelines Are Insufficient for Enterprise Release Decisions
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Testing, Tools, and Frameworks
  4. Deep Linking in Enterprise Android Apps: A Real-World, Scalable Approach

Deep Linking in Enterprise Android Apps: A Real-World, Scalable Approach

Enterprise apps demand reliable navigation. Learn to implement deep linking using Hilt, ViewModel, and UseCases, with real-world flows and built-in security.

By 
Mohan Sankaran user avatar
Mohan Sankaran
·
Oct. 16, 25 · Tutorial
Likes (6)
Comment
Save
Tweet
Share
1.7K Views

Join the DZone community and get the full member experience.

Join For Free

In modern enterprise Android development, navigating users through massive apps with multiple modules, features, and roles can quickly become complex. This is where deep linking steps in as a game-changer. It enables direct routing to specific parts of your app — skipping redundant screens and delivering a seamless, contextual experience.

In this article, we’ll walk through a real-world example and a modern architecture that makes deep linking not only functional but also secure, maintainable, and testable. We’ll use Jetpack components like ViewModel, dependency injection with Hilt, clean business logic through UseCases, and URI validation to keep things safe and predictable.

Real-World Example: Instant Checkout from a Promo Email

Imagine one of your users receives a promotional email from you. The subject line reads: “Limited Time Offer: Get 20% OFF Headphones Today!” Inside, there’s a shiny “Order Now” link. When tapped, instead of dropping the user into the home screen or product listing, it launches the app and opens directly to an order details screen for those headphones. The 20% discount is already applied, and there’s a single call to action: “Place Order.”

That’s the kind of flow that deep linking unlocks — minimal friction, high intent, and maximum conversion.

Instant checkout from a promo email

Now, let’s see how to build that flow.

1. Define the Deep Link in the Manifest

XML
 
<activity
    android:name=".OrderDetailsActivity"
    android:exported="true">
    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data
            android:scheme="myapp"
            android:host="order"
            android:path="/details" />
    </intent-filter>
</activity>


This config tells Android to launch OrderDetailsActivity when the user clicks on a link like:

Shell
 
myapp://order/details?orderId=827632&promo=SUMMER20


The autoVerify attribute ensures verified app links behave securely and predictably.

2. Validate the Deep Link Securely

Kotlin
 
sealed class ValidationResult {
    object Valid : ValidationResult()
    data class Invalid(val reason: String) : ValidationResult()
}

@Singleton
class DeepLinkValidator @Inject constructor() {
    fun validate(uri: Uri): ValidationResult {
        return when {
            uri.scheme != "myapp" -> ValidationResult.Invalid("Invalid scheme")
            uri.host != "order" -> ValidationResult.Invalid("Invalid host")
            !hasValidSignature(uri) -> ValidationResult.Invalid("Missing or invalid parameters")
            else -> ValidationResult.Valid
        }
    }

    private fun hasValidSignature(uri: Uri): Boolean {
        return uri.getQueryParameter("orderId") != null
    }
}


Before we do anything with a deep link, we want to make sure it’s valid. That means checking the scheme, host, and key parameters (like orderId) so we don’t accidentally process malformed or malicious URIs.

3. Activity Entry Point

Kotlin
 
@AndroidEntryPoint
class OrderDetailsActivity : AppCompatActivity() {
    private val viewModel: OrderDetailsViewModel by viewModels()

    @Inject
    lateinit var deepLinkValidator: DeepLinkValidator

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_order_details)

        intent.data?.let { uri ->
            when (val result = deepLinkValidator.validate(uri)) {
                is ValidationResult.Valid -> viewModel.processDeepLink(uri)
                is ValidationResult.Invalid -> {
                    Toast.makeText(this, "Invalid link: ${result.reason}", Toast.LENGTH_LONG).show()
                    finish()
                }
            }
        }

        observeViewModel()
    }

    private fun observeViewModel() {
        lifecycleScope.launchWhenStarted {
            viewModel.uiState.collect { state ->
                when (state) {
                    is OrderDetailsState.Success -> showOrder(state.order)
                    is OrderDetailsState.Error -> showError(state.error)
                    is OrderDetailsState.Loading -> showLoading()
                }
            }
        }
    }

    private fun showOrder(order: Order) { /* render UI */ }
    private fun showError(error: Throwable) { /* show error */ }
    private fun showLoading() { /* show loading spinner */ }
}


Once the URI is validated, we send it to the ViewModel, which handles the rest.

4. ViewModel + UseCase = Clean Business Logic

Kotlin
 
@HiltViewModel
class OrderDetailsViewModel @Inject constructor(
    private val getOrderUseCase: GetOrderUseCase
) : ViewModel() {

    private val _uiState = MutableStateFlow<OrderDetailsState>(OrderDetailsState.Loading)
    val uiState: StateFlow<OrderDetailsState> = _uiState

    fun processDeepLink(uri: Uri) {
        viewModelScope.launch {
            try {
                val orderId = uri.getQueryParameter("orderId")
                    ?: throw IllegalArgumentException("Missing order ID")
                val promoCode = uri.getQueryParameter("promo")
                val order = getOrderUseCase(orderId, promoCode)
                _uiState.value = OrderDetailsState.Success(order)
            } catch (e: Exception) {
                _uiState.value = OrderDetailsState.Error(e)
            }
        }
    }
}


The ViewModel parses the URI and invokes a use case to get the business data. If something goes wrong, it bubbles up the error gracefully.

5. Domain Logic and Models

Kotlin
 
class GetOrderUseCase @Inject constructor(
    private val orderRepository: OrderRepository
) {
    suspend operator fun invoke(orderId: String, promoCode: String?): Order {
        require(orderId.isNotBlank()) { "Order ID is required" }
        val order = orderRepository.getOrder(orderId)
            ?: throw IllegalArgumentException("Order not found")
        return order.apply {
            if (promoCode == "SUMMER20") {
                discount = "20%"
            }
        }
    }
}

sealed class OrderDetailsState {
    object Loading : OrderDetailsState()
    data class Success(val order: Order) : OrderDetailsState()
    data class Error(val error: Throwable) : OrderDetailsState()
}

data class Order(val id: String, val itemName: String, var discount: String? = null)


This use case handles validation, applies promotions, and returns structured domain models to the UI layer.

Test Your Deep Links

To quickly test your deep link behavior during development, you can use ADB to simulate clicking a link:

Shell
 
adb shell am start -a android.intent.action.VIEW \
    -c android.intent.category.BROWSABLE \
    -d "myapp://order/details?orderId=827632&promo=SUMMER20" \
    your.package.name


This command triggers the same flow your app would go through when a user taps a link in an email or browser.

You can also write a unit test to verify promo handling logic:

Kotlin
 
@Test
fun `test promo code is applied correctly`() = runTest {
    val uri = Uri.parse("myapp://order/details?orderId=987&promo=SUMMER20")
    viewModel.processDeepLink(uri)
    val state = viewModel.uiState.first()
    assertTrue(state is OrderDetailsState.Success)
    assertEquals("20%", (state as OrderDetailsState.Success).order.discount)
}


Unit tests make sure your business logic works and won’t break due to refactors or unexpected changes.

Wrapping Up

Deep linking isn’t just a convenience — it’s a competitive advantage, especially in enterprise apps where speed, personalization, and flow efficiency matter. Using URI validation, lifecycle-aware ViewModels, dependency injection with Hilt, and a clean separation of concerns, you can build deep linking flows that are secure, scalable, and a delight to use.

Deep linking Android (robot) apps

Opinions expressed by DZone contributors are their own.

Related

  • Get Ready for Android 15: Must-Have Testing Strategies for Effective Updates
  • How To Add Audio Animations to Android Apps
  • 9 Tips for Building Apps to Withstand AI-Driven Bot Attacks
  • Pragmatic Paths to On-Device AI on Android with ML Kit

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook