Achieving Smooth Routing in an Android App with Single Activity and Multiple Fragments

Sumeet Panchal
3 min readJul 24, 2024

--

In modern Android development, using a single Activity with multiple Fragments has become a standard practice. This approach offers better control over the app’s navigation flow, simplifies state management, and provides a more modular and reusable structure. In this blog, we’ll explore how to achieve smooth routing in an Android app with a single Activity and multiple Fragments. Additionally, we’ll discuss how to navigate to standalone Activities and SDK screens efficiently using an ActivityRouter class. We’ll also cover how to handle top navigation, showing either a back arrow or a cross icon.

Why Use a Single Activity with Multiple Fragments?

Using a single Activity with multiple Fragments provides several advantages:

1. Simplified Navigation: Managing the navigation stack becomes more straightforward when using Fragments within a single Activity.

2. Improved Performance: Fragment transitions are generally more efficient than launching new Activities.

3. Enhanced State Management: Handling configuration changes and maintaining UI state is easier with Fragments.

4. Reusability: Fragments can be reused across different parts of the app, promoting modular design.

Setting Up the ActivityRouter Class

To streamline navigation, we can create an ActivityRouter class. This class will manage Fragment transactions and handle navigation to standalone Activities and SDK screens. Here’s how to set it up:

Step 1: Create the ActivityRouter Class

First, create a new class called ActivityRouter:

import android.content.Context
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager

class ActivityRouter(private val activity: AppCompatActivity) {

private val fragmentManager: FragmentManager = activity.supportFragmentManager

private var navigationListener: NavigationListener? = null

interface NavigationListener {
fun onBackStackChanged()
}

fun setNavigationListener(listener: NavigationListener) {
this.navigationListener = listener
fragmentManager.addOnBackStackChangedListener {
listener.onBackStackChanged()
}
}

fun addFragment(fragment: Fragment, tag: String, addToBackStack: Boolean = true) {
val transaction = fragmentManager.beginTransaction()
transaction.add(R.id.fragment_container, fragment, tag)
if (addToBackStack) {
transaction.addToBackStack(tag)
}
transaction.commit()
}

fun replaceFragment(fragment: Fragment, tag: String, addToBackStack: Boolean = true) {
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, fragment, tag)
if (addToBackStack) {
transaction.addToBackStack(tag)
}
transaction.commit()
}

fun removeFragment(fragment: Fragment) {
val transaction = fragmentManager.beginTransaction()
transaction.remove(fragment)
transaction.commit()
}

fun navigateToActivity(intent: Intent) {
activity.startActivity(intent)
}

fun navigateToSDKScreen(context: Context, sdkActivityClass: Class<*>) {
val intent = Intent(context, sdkActivityClass)
activity.startActivity(intent)
}

fun navigateBack() {
if (fragmentManager.backStackEntryCount > 0) {
fragmentManager.popBackStack()
} else {
activity.finish()
}
}

fun clearBackStack() {
while (fragmentManager.backStackEntryCount > 0) {
fragmentManager.popBackStackImmediate()
}
}
}

Step 2: Define the Fragment Container

In your Activity’s layout file, define a container for the Fragments:

<!-- res/layout/activity_main.xml -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<androidx.appcompat.widget.Toolbar
android:id="@+id/tool_bar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar" />

<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

Step 3: Using the ActivityRouter Class

Now, let’s see how to use the ActivityRouter class in an Activity. Here’s an example:

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar

class MainActivity : AppCompatActivity(), ActivityRouter.NavigationListener {

private lateinit var activityRouter: ActivityRouter
private lateinit var toolbar: Toolbar

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

toolbar = findViewById(R.id.tool_bar)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)

activityRouter = ActivityRouter(this)
activityRouter.setNavigationListener(this)

// Example of navigating to a Fragment
val homeFragment = HomeFragment()
activityRouter.addFragment(homeFragment, "HomeFragment")
}

override fun onBackStackChanged() {
val canGoBack = supportFragmentManager.backStackEntryCount > 0
supportActionBar?.setDisplayHomeAsUpEnabled(canGoBack)
supportActionBar?.setHomeAsUpIndicator(if (canGoBack) R.drawable.ic_arrow_back else R.drawable.ic_cross)
}

private fun navigateToDetails() {
val detailsFragment = DetailsFragment()
activityRouter.replaceFragment(detailsFragment, "DetailsFragment")
}

private fun navigateToStandaloneActivity() {
val intent = Intent(this, StandaloneActivity::class.java)
activityRouter.navigateToActivity(intent)
}

private fun navigateToSDKScreen() {
// Replace `SDKActivity::class.java` with the actual SDK screen's Activity class
activityRouter.navigateToSDKScreen(this, SDKActivity::class.java)
}

override fun onBackPressed() {
activityRouter.navigateBack()
}

override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
}

Step 4: Handling Back Navigation

To handle back navigation, override the onBackPressed method in your Activity:

override fun onBackPressed() {
activityRouter.navigateBack()
}

Step 5: Clearing the Back Stack

If you need to clear the back stack at any point, you can use the clearBackStack method:

private fun clearNavigationStack() {
activityRouter.clearBackStack()
}

Step 6: Icons

Make sure you have the necessary icons in your drawable folder (ic_arrow_back and ic_cross).

Conclusion

Using a single Activity with multiple Fragments can greatly enhance the navigation experience in your Android app. By implementing an ActivityRouter class and handling top navigation updates, you can efficiently manage Fragment transactions and provide a smooth user experience. This approach not only simplifies your codebase but also improves performance and maintainability. Give it a try in your next project and experience the benefits of smooth routing in your app!

--

--

Sumeet Panchal
Sumeet Panchal

Written by Sumeet Panchal

Programming enthusiast specializing in Android and React Native, passionate about crafting intuitive mobile experiences and exploring innovative solutions.

No responses yet