0
0
Android Kotlinmobile~20 mins

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

Choose your learning style9 modes available
Build: UserProfileScreen
This screen shows a user's profile information fetched using a repository injected by Hilt.
Target UI
-------------------------
|      User Profile      |
|-----------------------|
| Name: Loading...       |
| Email: Loading...      |
|                       |
| [Refresh]             |
-------------------------
Use Hilt to inject a UserRepository into the ViewModel.
UserRepository provides user data (name and email).
ViewModel fetches user data from UserRepository.
UserProfileScreen observes ViewModel state and displays user info.
Add a Refresh button to reload user data.
Starter Code
Android Kotlin
package com.example.userprofile

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 androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

// TODO: Add User data class here

// TODO: Add UserRepository interface and implementation here

// TODO: Add ViewModel with injected UserRepository here

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

@Composable
fun UserProfileScreen() {
    // TODO: Obtain ViewModel and observe user state
    // TODO: Display user name and email or loading text
    // TODO: Add Refresh button to reload user data
}
Task 1
Task 2
Task 3
Task 4
Task 5
Task 6
Solution
Android Kotlin
package com.example.userprofile

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 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

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

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

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

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

    init {
        loadUser()
    }

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

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

import androidx.hilt.navigation.compose.hiltViewModel

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

    Column(modifier = Modifier.padding(16.dp)) {
        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 UserProfileViewModel. The repository provides user data asynchronously. The ViewModel exposes this data as a StateFlow which the composable observes to update the UI reactively.

The UserProfileScreen composable uses hiltViewModel() to get the ViewModel instance with dependencies injected. It shows loading text while data is null, then displays the user's name and email once loaded. The Refresh button triggers reloading the user data.

This pattern cleanly separates data fetching logic from UI, making the code easier to test and maintain.

Final Result
Completed Screen
-------------------------
|      User Profile      |
|-----------------------|
| Name: Alice Johnson    |
| Email: alice@example.com|
|                       |
| [Refresh]             |
-------------------------
When the screen loads, it shows 'Loading...' text briefly, then displays the user's name and email.
Tapping the Refresh button reloads the user data and updates the displayed info.
Stretch Goal
Add a loading spinner that shows while user data is being fetched.
💡 Hint
Use a Boolean state in ViewModel to track loading status and show a CircularProgressIndicator in the UI when loading.