I use Android + Kotlin + Proguard
Code
inferface Authentication {
val authenticated: Boolean
val lastAuthentication: Instant
fun authenticate(force: Boolean = false): Flow<Event>
suspend fun revokeAccess()
}
And an implementation of that interface
internal sealed class AuthenticationInternal(
private val trustProvider: TrustProvider) : Authentication {
override val authenticated: Boolean
get() = trustProvider.authenticatedBefore()
override val lastAuthentication: Instant
get() = trustProvider.getLastAuthenticatedTime()
override fun authenticate() = flow {
trustProvider.checkIntegrity()
emitAll(internalAuthenticate())
}.flowOn(Dispatchers.Default)
override suspend fun revokeAccess() {
trustProvider.revokeAccess()
}
}
-> During Kotlin Annotation Processing this interface gets (as expected) a default implementation:
Kotlin generated code:
Class: Authentication$DefaultImpls.dex
-> Now my issue:
Since this class is generated and not reachable from Kotlin code, how can I keep this class using progurard.
In my release build, this class is put out of the release dex and the application crashes with an RuntimeException.
Note:
Adding this to the proguard mappings file:
-keep class com.my.package.AuthenticationInternal$DefaultImpls { *; }
will not work, since it is not reachable
How can I keep kapt-generated classes with proguard?
Related
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
To provide the TripRepository you need to create a class with Hilt's Bind functions. Example:
#Module
#InstallIn(SingletonComponent::class)
interface RepositoryModule {
#Binds
#Singleton
fun bindTripRepository(
repository: TripRepository // here the class
): TripRepositoryContract // here the interface that the class implements
}
It is also necessary to modify the TripRepository. You must add the #Inject constructor() (even if your class doesn't use any dependencies) so that Hilt can create the class.
In your case it will look like this:
class TripRepository #Inject constructor(
val tripDao: TripDao
) : TripRepositoryContract {
// ...
}
The last change is in your MainModule:
#Module
#InstallIn(SingletonComponent::class)
object MainModule {
// providing the db implementation normally
#Provides
#Singleton
fun provideDb(application: Application): MExpenseDb {
return Room.databaseBuilder(
application,
MExpenseDb::class.java,
"MExpenseDb"
).build()
}
// providing your dao interface to be injected into TripRepository
#Provides
#Singleton
fun provideTripDao(
mExpenseDb: MExpenseDb
): TripDao = mExpenseDb.tripDao
}
Note that I changed from ActivityComponent to SingletonComponent in the hilt's modules, in both cases we want them to be singleton throughout the project, not just a component created for activity (which can also be singleton).
See components life cycles here.
I also recommend upgrading the Hilt version in your project, because you are using a very old one. The newest version is 2.42.
I think this video can help you better understand some things about Hilt. And there is also this repository of a project that uses Hilt together with Room that can be useful for you to consult.
Important edit:
In the TripListVm you are using the private val tripRepository: TripRepository (your class that implements the TripRepositoryContract), it is not recommended to directly inject the implementation class, instead you should inject the interface (TripRepositoryContract) and let Hilt take care of providing the implementation of it. Because that's what we taught Hilt to do in the RepositoryModule.
So to make it ideal, the TripListVm would look like this:
#HiltViewModel
class TripListVm #Inject constructor(
private val tripRepositoryContract: TripRepositoryContract
) : ViewModel() {
// ...
}
Error with Kotlin conversion from java, this project is testing Github Api and displaying data in recyclerview.I get the a compile error which i cannot workout for Dagger2, it worked in Java but when converting to Kotlin i get a compile error at runtime.
It seems to be with injecting an api method into the view model
i have tried following the error then cleaning and rebuilding the app
I have also tried invalidating caches and restarting but seems there is an error with the conversion into Kotlin from Java. Any help would be appreciated.
Here is my class:
class RepoRepository {
private lateinit var repoService: GithubRepos
#Inject
fun RepoRepository(repoService: GithubRepos) {
this.repoService = repoService
}
fun getRepositories(): Single<List<Repo>> {
return repoService.getRepos()
}
fun getSingleRepo(owner: String, name: String): Single<Repo> {
return repoService.getSingleRepo(owner, name)
}
}
My component class:
#Singleton
#Component(modules = arrayOf(NetworkModule::class))
interface AppComponent {
/**
* inject required dependencies into MainActivityListViewModel
*/
fun inject(mainActivityListViewModel: MainActivityListViewModel)
#Component.Builder
interface Builder {
fun build(): AppComponent
fun networkModule(networkModule: NetworkModule): Builder
}
}
And my ViewModel:
class MainActivityListViewModel : BaseViewModel() {
private lateinit var repoRepository: RepoRepository
private var disposable: CompositeDisposable? = null
private val repos = MutableLiveData<List<Repo>>()
private val repoLoadError = MutableLiveData<Boolean>()
private val loading = MutableLiveData<Boolean>()
#Inject
fun ListViewModel(repoRepository: RepoRepository) {
this.repoRepository = repoRepository
disposable = CompositeDisposable()
fetchRepos()
}
fun getRepos(): LiveData<List<Repo>> {
return repos
}
fun getError(): LiveData<Boolean> {
return repoLoadError
}
fun getLoading(): LiveData<Boolean> {
return loading
}
private fun fetchRepos() {
loading.value = true
disposable?.add(repoRepository.getRepositories()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object :
DisposableSingleObserver<List<Repo>>() {
override fun onSuccess(value: List<Repo>) {
repoLoadError.value = false
repos.value = value
loading.value = false
}
override fun onError(e: Throwable) {
repoLoadError.value = true
loading.value = false
}
}))
}
override fun onCleared() {
super.onCleared()
if (disposable != null) {
disposable!!.clear()
disposable = null
}
}
}
this is error i am getting:
[Dagger/MissingBinding] repos.network.RepoRepository cannot be
provided without an #Inject constructor or an #Provides-annotated
method. This type supports members injection but cannot be
implicitly provided.
public abstract repos.network.RepoRepository
repoRepository();
^
repos.network.RepoRepository is provided at
components.AppComponent.repoRepository() e: repos/di/components/AppComponent.java:19: error:
[Dagger/MissingBinding] repos.network.RepoRepository cannot be
provided without an #Inject constructor or an #Provides- annotated
method. This type supports members injection but cannot be
implicitly provided.
^
repos.network.RepoRepository is injected at
repos.viewmodels.MainActivityListViewModel.ListViewModel(repoRepository)
repos.viewmodels.MainActivityListViewModel is injected at
repos.di.components.AppComponent.inject(repos.viewmodels.MainActivityListViewModel)
Your error clearly says:
[Dagger/MissingBinding] repos.network.RepoRepository cannot be provided without an #Inject constructor or an #Provides-annotated method.
You didn't define constructor for your RepoRepository class.
It should look something like this:
class RepoRepository #Inject constructor(private val repoService: GithubRepos) {//the rest of your code here}
This goes for your viewmodel class as well.
If you are using android ViewModel architecture component i suggest you read this article which explains how to use it with Dagger2.
Hope this helps.
I'm trying MVVM, Dagger2, Retrofit and Coroutine. Now I have the problem that I can successfully inject a ProfileService into my activity, but not into my repository. I get a profileService lateinit property has not been initialized
//MainActivity
#Inject
lateinit var profileService: ProfileService //only for testing
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val getTheExpectedResult = profileService.hasValidName("asdffff") //true
profileViewModel.createProfile("ku") //throw the not initialized
}
The viewmodel calls the repository.
// profile repository
private fun getProfileRepository(userId: String = "", apiKey: String = ""): ProfileRepository {
return ProfileRepository(ApiFactory.getApi(userId, apiKey))
}
fun createProfile(name: String) {
scope.launch {
try {
val profile = getProfileRepository().createProfile(name)
profileLiveData.postValue(profile)
}
//...
In the repository I inject the profileService
class ProfileRepository(private val api: NIPApiInterface) {
#Inject
lateinit var profileService: ProfileService
suspend fun createProfile(name: String): ProfileResponse? {
if (!profileService.hasValidName(name)) { //throw the not initialized
//...
My unspectacular ProfileService
class ProfileService #Inject constructor() {
fun hasValidName(name: String): Boolean {
return name.length > 3
}
}
So that I don't post too much code, the following info. In my Application I seem to initialize everything correctly, because the Activity can access the ProfileService. Here are my Dagger configurations:
//AppComponent
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class, AppModule::class, ActivityBuilder::class
]
)
interface AppComponent : AndroidInjector<NIPApplication> {
#Component.Factory
interface Factory {
fun create(#BindsInstance application: NIPApplication): AppComponent
}
}
The AppModule
#Module
class AppModule {
#Provides
#Singleton
fun provideContext(app: NIPApplication): Context = app
}
If you need more code, please send a comment.
Dagger won't inject dependencies unless it creates an instance of a class that needs injection or is explicitly asked to. Since the ProfileRepository object is created by you, it won't get its dependencies injected by Dagger.
The best way to solve this is to let Dagger create ProfileRepository objects by #Inject-annotated constructor (or using #Provides-annotated method in a Dagger module (provider), however this seems quite redundant in this particular case):
class ProfileRepository #Inject constructor(
private val api: NIPApiInterface,
private val profileService
)
Note that now Dagger will want to inject an NIPApiInterface object as well, so you have to create a provider for that or remove it from the constructor and pass it in some other way.
I am using coroutines with room database and while fetching data from room I am getting below error
\app\build\generated\source\kapt\debug\co\location\locationapp\data\source\local\LocationDataDao_Impl.java:87:
error: method execute in class CoroutinesRoom cannot be applied to
given types;
return CoroutinesRoom.execute(__db, true, new Callable() {
^ required: RoomDatabase,Callable,Continuation found:
RoomDatabase,boolean,>,Continuation
reason: cannot infer type-variable(s) R
(actual and formal argument lists differ in length) where R is a type-variable:
R extends Object declared in method execute(RoomDatabase,Callable,Continuation) where
CAP#1 is a fresh type-variable:
CAP#1 extends Object super: Unit from capture of ? super Unit
Below is my Dao class
#Dao
interface LocationDataDao {
#Query("SELECT * FROM location limit :limit offset :offset")
suspend fun queryLocationData(limit: Int, offset: Int): List<Location>
#Query("DELETE FROM location")
suspend fun deleteAllLocationData(): Int
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertAllLocationData(locationData: List<Location>)
}
Below is my repository class
class LocationRepository #Inject
constructor(private val apiInterface: ApiInterface, private val locationDataDao: LocationDataDao) {
suspend fun getLocationDataFromApi(limit: Int, offset: Int): List<Location> {
try {
return apiInterface.getLocationData(offset, limit).await()
} catch (ex: HttpException) {
throw ApiError(ex)
}
}
suspend fun getLocationDataFromDb(limit: Int, offset: Int): List<Location> {
return locationDataDao.queryLocationData(limit, offset)
}
suspend fun insertData(locationList: List<Location>) {
locationDataDao.insertAllLocationData(locationList)
}
suspend fun deleteDataFromDB() {
locationDataDao.deleteAllLocationData()
}
}
I am fetching data from a method like below
fun loadAfter() {
Job job = CoroutineScope(Dispatchers.IO).launch {
val data = locationRepository.getLocationDataFromDb(BuildConfig.PAGE_SIZE, params.key)
}
}
Please let me know if further info is required
Make sure that all room libraries have the same version in your app build.gradle:
...
dependencies {
...
def room_version = "2.2.0-alpha02"
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
}
See the Declaring dependencies documentation section.
Make sure to upgrade to the latest version.
I upgraded from 2.2.6 to 2.3.0 and the problem has been solved.
def room_version = "2.3.0"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
I'm having some weird kotlin generic issues with Dagger that I kinda fixed but the solution isn't sound.
Here's the dagger classes:
#Module class P5Module {
#Provides fun pool(): RecyclerView.RecycledViewPool = RecyclerView.RecycledViewPool()
#Provides
fun adapters(fusion: P5FusionAdapter, personas: P5ListAdapter, skills: P5SkillsAdapter, info: InfoAdapter)
: List<Pageable> = listOf(fusion, personas, skills, info)
}
#ActivityScope
#Subcomponent(modules = arrayOf(P5Module::class)) interface P5Component {
fun adapter(): PageableAdapter
}
interface Pageable {
fun manager(ctx: Context): LayoutManager
fun attach()
fun adapter(): Adapter<*>
}
class PageableAdapter
#Inject constructor(val pageables: List<Pageable>, val pool: RecyclerView.RecycledViewPool) :PagerAdapter()
When I build, I get this kapt error in the top level component:
e: C:\Users\daykm\StudioProjects\P5Executioner\app\build\tmp\kapt3\stubs\appDebug\com\daykm\p5executioner\AppComponent.java:17: error: [com.daykm.p5executioner.main.P5Component.adapter()] java.util.List<? extends com.daykm.p5executioner.view.Pageable> cannot be provided without an #Provides-annotated method.
e:
e: public abstract interface AppComponent {
e: ^
e: java.util.List<? extends com.daykm.p5executioner.view.Pageable> is injected at
e: com.daykm.p5executioner.view.PageableAdapter.<init>(pageables, …)
e: com.daykm.p5executioner.view.PageableAdapter is provided at
e: com.daykm.p5executioner.main.P5Component.adapter()
e: java.lang.IllegalStateException: failed to analyze: org.jetbrains.kotlin.kapt3.diagnostic.KaptError: Error while annotation processing
I took a look at the stubs generated:
#javax.inject.Inject()
public PageableAdapter(#org.jetbrains.annotations.NotNull()
java.util.List<? extends com.daykm.p5executioner.view.Pageable> pageables, #org.jetbrains.annotations.NotNull()
android.support.v7.widget.RecyclerView.RecycledViewPool pool) {
super();
}
#org.jetbrains.annotations.NotNull()
#dagger.Provides()
public final java.util.List<com.daykm.p5executioner.view.Pageable> adapters(#org.jetbrains.annotations.NotNull()
Apparently doesn't match with because when I modify the dagger class like so:
#Module class P5Module {
#Provides fun pool(): RecyclerView.RecycledViewPool = RecyclerView.RecycledViewPool()
#Provides
fun adapters(fusion: P5FusionAdapter, personas: P5ListAdapter, skills: P5SkillsAdapter, info: InfoAdapter)
: List<*> = listOf(fusion, personas, skills, info)
}
#ActivityScope
#Subcomponent(modules = arrayOf(P5Module::class)) interface P5Component {
fun adapter(): PageableAdapter
}
interface Pageable {
fun manager(ctx: Context): LayoutManager
fun attach()
fun adapter(): Adapter<*>
}
class PageableAdapter
#Inject constructor(val pageables: List<*>, val pool: RecyclerView.RecycledViewPool) :PagerAdapter()
The issue disappears.
Am I bumping into some weird translation issue with covariance and contravariance? I'd rather not force a cast downstream.
Kotlin's List<out E> translates to covariance in Java's List<? extends E> by default. Dagger doesn't like that.
To get around this issue, you can either switch to MutableList<E> which is invariant, or write List<#JvmSuppressWildcards E>.