๐Ÿค– Android Dependency Injection

A Beginner's Guide to Building Better Android Apps

Understanding DI concepts, benefits, and practical implementation

Press โ†’ to continue

๐Ÿค” What is Dependency Injection?

๐Ÿ’กDependency Injection (DI) is a design pattern that provides dependencies to an object instead of the object creating them itself.

Simple Analogy:

Think of it like ordering food at a restaurant:

The object receives its dependencies from an external source rather than creating them internally.

๐ŸŽฏ Why Do We Need Dependency Injection?

โŒ Problems Without DI

  • Hard to test
  • Tight coupling
  • Code duplication
  • Difficult maintenance

โœ… Benefits With DI

  • Easy testing
  • Loose coupling
  • Code reusability
  • Better maintainability

๐Ÿš€DI makes your Android apps more modular, testable, and maintainable!

โŒ Bad Example: Without Dependency Injection

class UserRepository {
    private DatabaseHelper dbHelper;
    private NetworkService networkService;
    
    public UserRepository() {
        // Creating dependencies inside the class
        dbHelper = new DatabaseHelper();
        networkService = new NetworkService();
    }
    
    public User getUser(int id) {
        // Implementation using dbHelper and networkService
        return networkService.fetchUser(id);
    }
}

Problems with this approach:

โœ… Good Example: With Dependency Injection

class UserRepository {
    private DatabaseHelper dbHelper;
    private NetworkService networkService;
    
    // Dependencies are injected via constructor
    public UserRepository(DatabaseHelper dbHelper, 
                         NetworkService networkService) {
        this.dbHelper = dbHelper;
        this.networkService = networkService;
    }
    
    public User getUser(int id) {
        return networkService.fetchUser(id);
    }
}

Benefits of this approach:

๐Ÿ”ง Types of Dependency Injection

1. Constructor Injection

public UserService(UserRepository repository) { this.repository = repository; }

2. Setter Injection

public void setUserRepository(UserRepository repository) { this.repository = repository; }

3. Field Injection

@Inject UserRepository repository;

๐Ÿ’กConstructor injection is preferred because it ensures dependencies are available when the object is created.

๐Ÿ› ๏ธ Popular Android DI Frameworks

Framework Complexity Performance Best For
Dagger 2 High Excellent Large apps
Hilt Medium Excellent Most Android apps
Koin Low Good Kotlin projects

๐ŸŽฏHilt is Google's recommended solution for dependency injection in Android.

โšก Introduction to Hilt

๐Ÿ—๏ธHilt is built on top of Dagger 2 and provides a simpler way to implement DI in Android apps.

Key Benefits of Hilt:

Setup (build.gradle):

dependencies {
    implementation "com.google.dagger:hilt-android:2.44"
    kapt "com.google.dagger:hilt-compiler:2.44"
}

๐Ÿ“ฑ Step 1: Hilt Application Class

First, annotate your Application class with @HiltAndroidApp:

@HiltAndroidApp
class MyApplication : Application() {
    // Application logic here
}
            

And don't forget to register it in AndroidManifest.xml:

<application android:name=".MyApplication" android:label="@string/app_name" android:theme="@style/AppTheme"> ... </application>

๐Ÿ’กThis triggers Hilt's code generation and sets up the DI container.

๐Ÿญ Step 2: Creating Dependencies with @Module

Create a module to provide dependencies:

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    
    @Provides
    @Singleton
    fun provideRetrofit(): Retrofit {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
    
    @Provides
    fun provideApiService(retrofit: Retrofit): ApiService {
        return retrofit.create(ApiService::class.java)
    }
}

๐Ÿ”ง@InstallIn specifies which component should contain the module.

๐ŸŽฌ Step 3: Injecting into Activities

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    
    @Inject
    lateinit var userRepository: UserRepository
    
    @Inject
    lateinit var apiService: ApiService
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        // Use injected dependencies
        loadUserData()
    }
    
    private fun loadUserData() {
        // userRepository and apiService are ready to use!
    }
}

โœจ@AndroidEntryPoint enables DI for Android components.

๐Ÿ›๏ธ Step 4: Injecting into ViewModels

@HiltViewModel
class UserViewModel @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {
    
    private val _users = MutableLiveData<List<User>>()
    val users: LiveData<List<User>> = _users
    
    fun loadUsers() {
        viewModelScope.launch {
            val userList = userRepository.getUsers()
            _users.value = userList
        }
    }
}

In your Activity/Fragment:

@AndroidEntryPoint class MainActivity : AppCompatActivity() { private val viewModel: UserViewModel by viewModels() // ViewModel with dependencies is automatically created! }

๐Ÿ”จ Constructor Injection Example

For classes you own, use @Inject on the constructor:

class UserRepository @Inject constructor(
    private val apiService: ApiService,
    private val userDao: UserDao
) {
    
    suspend fun getUsers(): List<User> {
        return try {
            val remoteUsers = apiService.getUsers()
            userDao.insertUsers(remoteUsers)
            remoteUsers
        } catch (e: Exception) {
            userDao.getAllUsers()
        }
    }
    
    suspend fun getUserById(id: Int): User? {
        return userDao.getUserById(id)
    }
}

โšกHilt automatically knows how to create this class and inject its dependencies!

๐ŸŽฏ Understanding Scopes

Scopes control the lifetime of dependencies:

Scope Lifetime Use Case
@Singleton App lifetime Database, Network clients
@ActivityScoped Activity lifetime Activity-specific services
@FragmentScoped Fragment lifetime Fragment-specific data
@Provides @Singleton fun provideDatabase(): AppDatabase { // This will be created only once }

๐Ÿงช Testing with Dependency Injection

DI makes testing much easier by allowing mock dependencies:

@HiltAndroidTest
class UserRepositoryTest {
    
    @get:Rule
    var hiltRule = HiltAndroidRule(this)
    
    @Inject
    lateinit var userRepository: UserRepository
    
    @Before
    fun setup() {
        hiltRule.inject()
    }
    
    @Test
    fun testGetUsers() {
        // Test your repository with injected dependencies
        val users = userRepository.getUsers()
        assertNotNull(users)
    }
}

๐ŸŽญYou can easily replace real dependencies with test doubles using @TestInstallIn.

โญ Best Practices

DO:

DON'T:

Remember: DI should make your code simpler, not more complex!

๐ŸŒ Real-world Example: Complete Setup

Here's how all pieces fit together:

// 1. Application Class
@HiltAndroidApp
class MyApp : Application()

// 2. Module
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @Provides @Singleton
    fun provideUserRepo(api: ApiService) = UserRepository(api)
}

// 3. Activity
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private val viewModel: UserViewModel by viewModels()
}

// 4. ViewModel
@HiltViewModel
class UserViewModel @Inject constructor(
    private val repository: UserRepository
) : ViewModel()

โš ๏ธ Common Mistakes to Avoid

โŒ Common Mistakes

  • Forgetting @AndroidEntryPoint
  • Wrong component in @InstallIn
  • Creating circular dependencies
  • Not handling nullable dependencies

โœ… Solutions

  • Always annotate entry points
  • Choose appropriate components
  • Design dependencies carefully
  • Use proper nullability annotations

๐Ÿ’กMost errors are caught at compile-time, making debugging easier!

โšก Performance Considerations

Hilt Performance Benefits:

Tips for Better Performance:

@Provides
@Singleton
fun provideExpensiveService(): ExpensiveService {
    return ExpensiveService() // Created only once
}

๐ŸŽ‰ Conclusion

What We've Learned:

๐Ÿš€ Next Steps:

Start implementing DI in your Android projects! Begin with simple dependencies and gradually adopt more advanced features.

๐Ÿ’ปHappy Coding with Dependency Injection!

1 / 20