I am new to Android dev and need to build my first project for university. The university was using the old XML version with Java, but I wanted to learn Compose so I learnt Kotlin.
Now after getting everything setup, I am trying to use hiltViewModel() method to inject the view model inside the composable function and I am getting an error. I watched this tutorial: https://www.youtube.com/watch?v=A7CGcFjQQtQ&t=10s and a few other stack overflow questions which suggest doing the same thing but I am not sure what is going on.
After getting this to work. Now it says a database class implementation is not found, but I expect Dagger Hilt to produce this? for Room database
Here is the basic code:
Dependencies:
build.gradle:
buildscript {
ext {
compose_version = '1.0.0'
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:7.0.0"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10"
//->Adding the Dagger Hilt class path here as suggested:
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.38.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
app/build.gradle:
plugins {
id 'com.android.application'
id 'kotlin-android'
//->Adding Kotlin annotation processing plugin and the dagger hilt plugin:
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
android {
compileSdk 31
defaultConfig {
applicationId "ac.gre.mxpenseresit"
minSdk 21
targetSdk 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
useIR = true
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
kotlinCompilerVersion '1.5.10'
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'com.google.android.material:material:1.6.1'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
implementation 'androidx.activity:activity-compose:1.4.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
//This version for navigation is hard coded, probably will need to update later, but should be fine for assignment
implementation("androidx.navigation:navigation-compose:2.4.2")
//Room Dependencies:
def room_version = "2.4.2"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
//Coroutine dependency:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
//->Dagger Hilt Dependency for DI copied from official docs:
implementation "com.google.dagger:hilt-android:2.38.1"
kapt "com.google.dagger:hilt-compiler:2.38.1"
}
import android.app.Application import dagger.hilt.android.HiltAndroidApp
/**
* Adding the Hilt Android App dependency for the Application class
*/
@HiltAndroidApp
class MExpenseApp : Application() {
}
Main & only activity:
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MXPenseResitTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
App(modifier = Modifier)
}
}
}
}
}
Main Module:
**
* Setup dependencies:
*/
@Module
@InstallIn(ActivityComponent::class)
object MainModule {
@Provides
@Singleton
fun provideDb(application: Application): MExpenseDb {
return Room
.databaseBuilder(
application,
MExpenseDb::class.java,
"MExpenseDb"
).build()
}
/**
* We are type hinting to the Interface so we can change implementations
*/
@Provides
@Singleton
fun provideTripRepository(mExpenseDb: MExpenseDb): TripRepositoryContract {
return TripRepository(mExpenseDb.tripDao)
}
}
View Model:
@HiltViewModel
class TripListVm @Inject constructor(
private val tripRepository: TripRepository
) : ViewModel() {
/**
* @var trips
* Get all trips
*/
val trips = tripRepository.getTrips()
val testStr: String = "Test String!"
}
Composable:
@Composable
fun TripList(
navController: NavHostController,
modifier: Modifier = Modifier,
tripListVm: TripListVm = hiltViewModel()
) {
Text(text = "Trip List")
TripListItem(
name = "Trip Name",
date = "16/04/1885",
amount = 46.66,
modifier = modifier
)
}
Result:
I am also getting an error: [Dagger/MissingBinding] exception. ac.gre.mxpenseresit.data.repository.TripRepository cannot be provided without an @Inject constructor or an @Provides-annotated method
Here is the code for the data layer:
Database class:
@Database(
entities = [
Trip::class,
Expense::class,
],
version = 1
)
abstract class MExpenseDb : RoomDatabase() {
abstract val tripDao: TripDao
}
Dao:
@Dao
interface TripDao {
/**
* Gets all trips
*/
@Query("SELECT * FROM Trip")
fun getTrips(): Flow<List<Trip>>
/**
* Gets trips based on a named search,
* This functionality can be extended later
*/
@Query("SELECT * FROM Trip t WHERE t.name LIKE :name")
suspend fun getTripsBySearchName(name: String)
/**
* Gets trip by Id
*/
@Query("SELECT * FROM Trip WHERE Trip.id = :id")
suspend fun getTripById(id: Long)
/**
*
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun saveTrip(trip: Trip): Long
@Delete
suspend fun deleteTrip(trip: Trip)
}
Trip Repository Contract (Interface):
interface TripRepositoryContract {
fun getTrips(): Flow<List<Trip>>
suspend fun getTripById(id: Long): Trip?
suspend fun getTripsBySearchName(keyword: String): List<Trip>
suspend fun saveTripLocal(trip: Trip)
suspend fun saveTripRemote(trip: Trip)
suspend fun deleteTrip(trip: Trip)
}
Trip Repository implementation:
class TripRepository (val tripDao: TripDao) : TripRepositoryContract {
override fun getTrips(): Flow<List<Trip>> {
return tripDao.getTrips();
}
override suspend fun getTripById(id: Long): Trip? {
TODO("Not yet implemented")
}
override suspend fun getTripsBySearchName(keyword: String): List<Trip> {
TODO("Not yet implemented")
}
override suspend fun saveTripLocal(trip: Trip) {
TODO("Not yet implemented")
}
override suspend fun saveTripRemote(trip: Trip) {
TODO("Not yet implemented")
}
override suspend fun deleteTrip(trip: Trip) {
TODO("Not yet implemented")
}
}
Now the hiltViewModel() method works correctly, but I am getting a MExpenseDb_impl class not found
I looked at this stack overflow question: Android room persistent: AppDatabase_Impl does not exist
And it says to use the kept dependency, I already have the same thing with annotationProcessing so I'm not sure if this is an issue
All the tutorials online are either too long & irrelevant or too vague and I am trying to gain a deeper understanding about how this works. Any advice would be appreciated