0
0
Android Kotlinmobile~20 mins

Dependency injection with Hilt in depth in Android Kotlin - Mini App: Build & Ship

Choose your learning style9 modes available
Build: User Profile Screen
This screen shows a user's profile information fetched from a repository. It uses Hilt for dependency injection to provide the repository to the ViewModel.
Target UI
-------------------------
|      User Profile      |
-------------------------
| Name: Loading...       |
| Email: Loading...      |
|                       |
| [Refresh]             |
-------------------------
Use Hilt to inject UserRepository into UserViewModel
UserRepository provides user data (name and email)
UserViewModel fetches user data from UserRepository
UserProfileScreen observes ViewModel and displays user info
Add a Refresh button to reload user data
Show 'Loading...' text while data is loading
Starter Code
Android Kotlin
package com.example.hiltdemo

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

// TODO: Define User data class
// TODO: Define UserRepository interface and implementation
// TODO: Setup Hilt module to provide UserRepository
// TODO: Create UserViewModel with injected UserRepository
// TODO: Build UserProfileScreen composable to display user info

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      MaterialTheme {
        Surface(modifier = Modifier.padding(16.dp)) {
          UserProfileScreen()
        }
      }
    }
  }
}

@Composable
fun UserProfileScreen() {
  // TODO: Get UserViewModel instance
  // TODO: Observe user data state
  // TODO: Display user name and email or Loading...
  // TODO: Add Refresh button to reload data
}
Task 1
Task 2
Task 3
Task 4
Task 5
Task 6
Task 7
Task 8
Task 9
Solution
Android Kotlin
package com.example.hiltdemo

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Inject
import javax.inject.Singleton
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import androidx.hilt.navigation.compose.hiltViewModel

// Data class representing a user
data class User(val name: String, val email: String)

// Repository interface
interface UserRepository {
  suspend fun getUser(): User
}

// Repository implementation
class UserRepositoryImpl @Inject constructor() : UserRepository {
  override suspend fun getUser(): User {
    // Simulate network or database delay
    kotlinx.coroutines.delay(1000)
    return User(name = "Alice Johnson", email = "alice@example.com")
  }
}

// Hilt module to provide UserRepository
@Module
@InstallIn(SingletonComponent::class)
object UserRepositoryModule {
  @Provides
  @Singleton
  fun provideUserRepository(): UserRepository = UserRepositoryImpl()
}

// ViewModel with injected UserRepository
@HiltViewModel
class UserViewModel @Inject constructor(
  private val userRepository: UserRepository
) : ViewModel() {
  private val _user = MutableStateFlow<User?>(null)
  val user: StateFlow<User?> = _user

  init {
    loadUser()
  }

  fun loadUser() {
    viewModelScope.launch {
      _user.value = null // Show loading
      val fetchedUser = userRepository.getUser()
      _user.value = fetchedUser
    }
  }
}

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      MaterialTheme {
        Surface(modifier = Modifier.padding(16.dp)) {
          UserProfileScreen()
        }
      }
    }
  }
}

@Composable
fun UserProfileScreen(viewModel: UserViewModel = hiltViewModel()) {
  val userState by viewModel.user.collectAsState()

  Column {
    if (userState == null) {
      Text(text = "Name: Loading...")
      Text(text = "Email: Loading...")
    } else {
      Text(text = "Name: ${userState!!.name}")
      Text(text = "Email: ${userState!!.email}")
    }
    Button(onClick = { viewModel.loadUser() }, modifier = Modifier.padding(top = 16.dp)) {
      Text("Refresh")
    }
  }
}

This solution uses Hilt to inject the UserRepository into the UserViewModel. The UserRepositoryImpl simulates fetching user data with a delay. The UserViewModel exposes a StateFlow of User? to represent loading and loaded states.

The UserProfileScreen composable gets the ViewModel instance via hiltViewModel(), observes the user state, and shows "Loading..." text while data is null. When data is loaded, it displays the user's name and email. The Refresh button calls loadUser() to reload data.

This setup demonstrates dependency injection with Hilt, ViewModel state management, and Compose UI updates reacting to state changes.

Final Result
Completed Screen
-------------------------
|      User Profile      |
-------------------------
| Name: Alice Johnson    |
| Email: alice@example.com|
|                       |
| [Refresh]             |
-------------------------
When the screen loads, it shows 'Loading...' for name and email for about 1 second.
Then it displays the user's name and email.
Tapping the Refresh button reloads the user data and shows 'Loading...' again briefly.
Stretch Goal
Add a loading spinner while user data is loading instead of just text.
💡 Hint
Use Compose's CircularProgressIndicator composable and show it conditionally when user data is null.