I have a problem with injection dagger into view model.The problem is that losing form inputs when rotating the screen.Is the problem in my injection or initialize my view model? Here is my viewmodel;
#HiltViewModel
class ProfilIslemViewModel #Inject constructor(application: Application,
private val kullaniciService: KullaniciService,
private val kullaniciDao:KullaniciDao
): AndroidViewModel(application), CoroutineScope {...}
And here is my fragment that use this view model;
#AndroidEntryPoint
class ProfilIslemFragment:Fragment(), ProfilIslemFragmentClickListener {
private val viewModel: ProfilIslemViewModel by viewModels();
...
}
Here is the module that injected class;
#Module
#InstallIn(SingletonComponent::class)
object KutuphanemAppModule {
#Singleton
#Provides
fun provideKutuphanemDatabase
(#ApplicationContext context:Context) = Room.databaseBuilder(
context,
KutuphanemDatabase::class.java,
KUTUPHANEM_DB_NAME
).build();
#Singleton
#Provides
fun provideRetrofit(client: OkHttpClient):Retrofit =
Retrofit.Builder()
.baseUrl(API_URL)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
#Singleton
#Provides
fun provideHttpClient(customSharedPreferences: CustomSharedPreferences):OkHttpClient{
return OkHttpClient.Builder().addInterceptor(object:Interceptor{
override fun intercept(chain: Interceptor.Chain): Response {
val request:Request = chain.request().newBuilder().addHeader("Authorization",
"Bearer "+customSharedPreferences.getStringFromSharedPreferences(APP_TOKEN_KEY).trim()).build();
return chain.proceed(request);
}
}).build();
}
#Singleton
#Provides
fun provideParametreDao(database: KutuphanemDatabase) = database.getParametreDao();
#Singleton
#Provides
fun provideParametreApi(retrofit: Retrofit):IParametreService = retrofit.create(IParametreService::class.java);
#Singleton
#Provides
fun provideKullaniciApi(retrofit: Retrofit):KullaniciService = retrofit.create(KullaniciService::class.java);
#Singleton
#Provides
fun provideKitapApi(retrofit: Retrofit):IKitapService = retrofit.create(IKitapService::class.java);
#Singleton
#Provides
fun provideKullaniciDao(database: KutuphanemDatabase) = database.getKullaniciDao();
}
When I rotate the screen the inputs are losing. How to solve this problem?
I found the error. It is not relevant my view model or injections. It caused by two way databinding. But even so I put this line into the contructor of viewmodel;
private val savedStateHandle: SavedStateHandle
I am controlling the state with this param.So my inputs are not losing.
Related
I wrote network module in project. I checked all similiar codes and qustions there are some people have same problem. But they all didn't solve this problem for me. I don't know what is problem here.
This is the error:
error: #Provides methods can only be present within a #Module or #ProducerModule
public static final com.technoface.iga.api.BoardingPassService postBoardingPassesRead(#org.jetbrains.annotations.NotNull()
NetworkModule.kt
#Module
#InstallIn(SingletonComponent::class)
object NetworkModule {
#Singleton
#Provides
fun provideBaseUrl() = BuildConfig.BASE_URL
#Provides
#Singleton
fun provideLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
}
#ServiceInterceptorOkHttpClient
#Singleton
#Provides
fun providesServiceInterceptor(clientPreferences: ClientPreferences): ServiceInterceptor =
ServiceInterceptor(clientPreferences)
#Provides
#Singleton
fun provideOkHttpClient(
logging: HttpLoggingInterceptor,
#ServiceInterceptorOkHttpClient
serviceInterceptor: ServiceInterceptor
): OkHttpClient {
return OkHttpClient.Builder()
.readTimeout(60, TimeUnit.SECONDS)
.connectTimeout(60, TimeUnit.SECONDS)
.addInterceptor(logging)
.addInterceptor(serviceInterceptor)
.build()
}
#Provides
#Singleton
fun provideNetworkResultCallAdapterFactory(): CallAdapter.Factory {
return NetworkResultCallAdapterFactory.create()
}
#Provides
#Singleton
fun provideRetrofit(client: OkHttpClient, callAdapterFactory: CallAdapter.Factory): Retrofit {
return Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(callAdapterFactory)
.client(client)
.build()
}
#Provides
#Singleton
fun postBoardingPassesRead(serviceBuilder: ServiceBuilder): BoardingPassService = serviceBuilder.buildService(BoardingPassService::class.java)
#Qualifier
#Retention(AnnotationRetention.BINARY)
annotation class BaseUrl
#Qualifier
#Retention(AnnotationRetention.BINARY)
annotation class ServiceInterceptorOkHttpClient
BoardingPassServiceRepo.kt
class BoardingPassServiceRepo #Inject constructor(
private val boardingPassService: BoardingPassService
) {
suspend fun postBoardingPassesRead(boardingPassInfoRequest: BoardingPassInfoRequest) = boardingPassService.postBoardingPassesRead(
encryptData(boardingPassInfoRequest)
)
}
AppModule.kt
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Singleton
#Provides
fun provideClientPreferences(
#ApplicationContext context: Context
) = ClientPreferences(context)
}
class BoardingPassServiceRepo #Inject constructor(
private val boardingPassService: BoardingPassService
) {
suspend fun postBoardingPassesRead(boardingPassInfoRequest: BoardingPassInfoRequest) = boardingPassService.postBoardingPassesRead(
encryptData(boardingPassInfoRequest)
)
}
Update Gradle version.
Changed gradle version.
Clean, Rebuild, Invalidate cache
Deactivated Antivirus.
Base app architecture for Android client-server app is:
UI (Activity + Fragment or Jetpack Compose)
ViewModel (also is part of UI actually)
Data (repositories to get data from database (Room) and API (Retrofit))
Hilt has different Components and Scopes for Android https://dagger.dev/hilt/components.html
For my base app architecture I have set DI using Hilt in the following way:
Database
#InstallIn(SingletonComponent::class)
#Module
object DatabaseModule {
#Provides
#Singleton // only this one is marked as #Singleton to get the same instance all the time (logically for Database...)
fun provideDataBase(#ApplicationContext context: Context): AppDataBase {
return AppDataBase.getInstance(context)
}
#Provides
fun provideUsersDao(dataBase: AppDataBase): UsersDao = dataBase.getUsersDao()
}
Network (Datasource)
#InstallIn(SingletonComponent::class)
#Module
object NetworkModule {
#Provides
#Singleton // also as Room instance, it should return the same instance of http client
fun providesOkHttpClient(
httpLoggingInterceptor: HttpLoggingInterceptor,
...
): OkHttpClient = OkHttpClient.Builder()
.addInterceptor(headerInterceptor)
...
.build()
#Provides
#Singleton
fun provideRetrofit(
gson: Gson,
client: OkHttpClient,
queryConverterFactory: Converter.Factory
): Retrofit = Retrofit.Builder()
...
.client(client)
.build()
//... some other stuff needed to setup OkHttp and Retrofit
#Provides
fun provideUserApi(retrofit: Retrofit): UserApi = retrofit.create(UserApi::class.java)
#Provides
fun provideUserDataSource(
userApi: UserApi,
networkErrorConverterHelper: NetworkErrorConverterHelper
): UserDataSource = UserDataSourceImpl(userApi, networkErrorConverterHelper)
}
Repository
#InstallIn(SingletonComponent::class)
#Module
interface RepositoryModule {
#Binds
fun bindUserRepositoryImpl(impl: UserRepositoryImpl): UserRepository
}
class UserRepositoryImpl #Inject constructor(
private val userDataSource: UserDataSource,
private val userDbManager: UserDbManager
) : UserRepository {
override fun observeCachedUsers(): Flow<List<User>> ...
override fun fetchUsers(): Flow<Resource<Unit>> = ...
}
ViewModel
#HiltViewModel
class UsersViewModel #Inject constructor(
private val userRepository: UserRepository,
) : ViewModel() {
...
}
Activity
#AndroidEntryPoint
class UsersActivity : AppCompatActivity() {
val userViewModel: UserViewModel by viewModels()
...
}
So basically everywhere I have used SingletonComponent for Hilt modules and #Singleton annotation for some providers so it would return the same instance (of Database, Http client)
I'm trying to understand how good (correct) all of that and in which cases I could use other components/scopes based on this sample.
When app has only one activity and uses Fragments or Jetpack Compose to implement all screens then I guess I can use ActivityRetainedComponent instead of SingletonComponent for data modules, in this case Hilt releases all instances in memory when a user presses the back button to exit the app (activity is destroyed), in case of SingletonComponent these instances live until process of the app dies, so basically difference in this case is not big but still...
Hello folks please help me to debug this dagger2 issue,I am very new to dagger2 and just started with a project.
public abstract interface ApplicationComponent {
^
java.util.Map<java.lang.Class<?
>,javax.inject.Provider<dagger.android.AndroidInjector.Factory<?>>> is injected at
dagger.android.DispatchingAndroidInjector(injectorFactoriesWithClassKeys, �)
dagger.android.DispatchingAndroidInjector<java.lang.Object> is injected at
com.example.dictionary.App.dispatchingServiceInjector
com.example.dictionary.App is injected at
com.example.dictionary.di.components.ApplicationComponent.inject(com.example.dictionary.App)
error: [Dagger/MissingBinding]
java.util.Map<java.lang.String,javax.inject.Provider<dagger.android.AndroidInjector.Factory<?
>>> cannot be provided without an #Provides-annotated method.
My implementations are:
interface ApiService {
companion object {
const val BASE_URL= "https://od-api.oxforddictionaries.com/"
}
#GET("api/v2/entries/en-gb/")
fun getMeaning(#Query("word") word: String): Observable<Response>
}
}
open class RemoteDataSource #Inject constructor(private val apiService: ApiService) {
fun getWordMeaning(word: String) = apiService.getMeaning(word)
}
#ApplicationScope
#Component(modules = [ApplicationModule::class, NetworkModule::class])
interface ApplicationComponent {
fun inject(app: App)
fun inject(mainActivity: MainActivity)
}
#Module
class ApplicationModule(private val application: Application) {
#ApplicationScope
#Provides
fun provideApplicationContext(): Context = application.applicationContext
#ApplicationScope
#Provides
fun provideSharedPreferences(): SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(application)
#ApplicationScope
#Provides
fun providerDisplayMetrics(): DisplayMetrics = application.resources.displayMetrics
}
#Module
class NetworkModule(
private val baseUrl: String,
private val application: Application
) {
companion object {
const val CACHE_SIZE = 10 * 1024 * 1024L // 10 MiB
const val TIME_OUT = 10L // time in minutes to get the response from server
}
#ApplicationScope
#Provides
fun provideGson(): Gson = GsonBuilder().create()
#ApplicationScope
#Provides
fun provideOkHttpCache() = Cache(application.cacheDir, CACHE_SIZE)
#ApplicationScope
#Provides
fun provideOkHttpClient(cache: Cache): OkHttpClient = with(OkHttpClient.Builder()) {
writeTimeout(3, TimeUnit.MINUTES)
.connectTimeout(3, TimeUnit.MINUTES)
.readTimeout(TIME_OUT, TimeUnit.MINUTES)
cache(cache)
addInterceptor(headersInterceptor())
build()
}
#ApplicationScope
#Provides
fun provideRetrofit(gson: Gson, okHttpClient: OkHttpClient): Retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
#ApplicationScope
#Provides
fun provideApiService(retrofit: Retrofit): ApiService =
retrofit.create(ApiService::class.java)
private var authRequest: String? = null
private fun headersInterceptor() = Interceptor { chain ->
val requestBuilder = chain.request().newBuilder()
requestBuilder.addHeader("Accept", "application/json")
requestBuilder.addHeader("app_id", "SOME APP ID")
requestBuilder.addHeader("app_key", "Some API KEY")
chain.proceed(
requestBuilder.build()
)
}
}
class App : Application(), HasAndroidInjector {
#Inject
lateinit var dispatchingServiceInjector:
DispatchingAndroidInjector<Any>
override fun onCreate() {
super.onCreate()
applicationComponent.inject(this)
RxJavaPlugins.setErrorHandler {
it.printStackTrace()
}
}
override fun androidInjector(): AndroidInjector<Any> {
return dispatchingServiceInjector
}
val applicationComponent: ApplicationComponent by
lazy<ApplicationComponent>(mode = LazyThreadSafetyMode.NONE) {
DaggerApplicationComponent
.builder()
.applicationModule(ApplicationModule(this))
.networkModule(NetworkModule(ApiService.BASE_URL, this))
.build()
}
}
I tried re-building the code, also attempted invalidate cache and restart to re-install dependencies, as dagger2 requires re-building the project every time once a new dependency injection is implemented, followed YouTube tutorials, to implement this so far, but unable to detect the problem, although in the build crash its indicating to provide #Provides, but in my NetworkModule, ApplicationModule I set this #Provides annotation.
Please help to figure out the solution.
As suggested in this answer, just add your App class in the android manifest under the application tag.
<application ...
android:name = ".App" ...>
...
I just got a problem to inject repository in the interceptor to get access token when its needed or rather expired. I just don't understand where I did something wrong. I just didn't find any example how to deal with interceptor and repository. Thinking about that double "Retrofit.Builder" maybe this is a problem. What do you think? And let's the code talk:
#Module
class AppModule {
#Singleton
#Provides
fun provideRefreshTokenService(client: OkHttpClient): RefreshTokenApi {
return Retrofit.Builder()
.baseUrl("https://id.twitch.tv/oauth2/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(RefreshTokenApi::class.java)
}
#Singleton
#Provides
fun provideHttpClient(headerInterceptor: HeaderInterceptor): OkHttpClient {
return OkHttpClient.Builder()
.addNetworkInterceptor(headerInterceptor)
.build()
}
#Singleton
#Provides
fun provideRetrofit(client: OkHttpClient): TwichApi {
return Retrofit.Builder()
.baseUrl("https://api.igdb.com/v4/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(TwichApi::class.java)
}
}
class TwichHeaderRepository #Inject constructor(
private val refreshTokenApi: RefreshTokenApi)
{
suspend fun fetchRefreshToken(): Response<RefreshToken> {
return withContext(Dispatchers.IO) {
refreshTokenApi.getRefreshToken()
}
}
}
private const val TAG = "AddRepositoryAction"
private const val HEADER_CLIENT_ID = "Client-ID"
private const val HEADER_AUTHORIZATION = "Authorization"
private const val HEADER_ACCEPT = "Accept"
private const val DEFAULT_ACCESS_TOKEN = "mjycvndz4sasons2mg990kqme6vu6d"
private const val UNAUTHORIZED_STATUS_CODE = 401
#Singleton
class HeaderInterceptor #Inject constructor(
private val context: Context,
private val twichHeaderRepository: TwichHeaderRepository
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request().newBuilder().apply {
header(HEADER_CLIENT_ID, "i3nzc6w3n0pod39zgsq8h445o2yp6p")
header(HEADER_AUTHORIZATION, "Bearer $DEFAULT_ACCESS_TOKEN")
header(HEADER_ACCEPT, "application/json")
}.build())
val refreshToken = runBlocking { generateAccessToken(twichHeaderRepository) }
if (response.code == UNAUTHORIZED_STATUS_CODE) {
response.close()
val accessToken = ""
return chain.proceed(chain.request().newBuilder().apply {
header(HEADER_CLIENT_ID, "i3nzc6w3n0pod39zgsq8h445o2yp6p")
header(HEADER_AUTHORIZATION, "Bearer $accessToken")
header(HEADER_ACCEPT, "application/json")
}.build())
}
return response
}
}
private suspend fun generateAccessToken(twichHeaderRepository: TwichHeaderRepository): String
{
val responseRefreshToken = twichHeaderRepository.fetchRefreshToken()
return responseRefreshToken.body().toString()
}
interface RefreshTokenApi {
#POST(
...
)
suspend fun getRefreshToken(
): Response<RefreshToken>
}
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
#Component.Factory
interface Factory {
fun create(#BindsInstance context: Context): AppComponent
}
fun inject(activity: MainActivity)
fun inject(fragment: AddFragment)
fun inject(interceptor: HeaderInterceptor)
}
class TwichRepository #Inject constructor(
private val twichApi: TwichApi
) {
suspend fun searchGames(title: String): Response<Game> {
return withContext(Dispatchers.IO) { twichApi.getGamesBySearch(title) }
}
}
Error message is
Found a dependency cycle okhttp3.OkHttpClient is injected at
com.example.glc.AppModule.provideRefreshTokenService(client)
com.example.glc.add.RefreshTokenApi is injected at
com.example.glc.add.TwichHeaderRepository(refreshTokenApi)
com.example.glc.add.TwichHeaderRepository is injected at
com.example.glc.add.HeaderInterceptor(�, twichHeaderRepository)
com.example.glc.add.HeaderInterceptor is injected at
com.example.glc.AppModule.provideHttpClient(headerInterceptor)
okhttp3.OkHttpClient is injected at
com.example.glc.AppModule.provideRetrofit(client)
com.example.glc.add.TwichApi is injected at
com.example.glc.add.TwichRepository(twichApi)
com.example.glc.add.TwichRepository is injected at
com.example.glc.add.AddViewModel(twichRepository)
com.example.glc.add.AddViewModel is injected at
com.example.glc.add.AddFragment.addViewModel
com.example.glc.add.AddFragment is injected at
com.example.glc.di.AppComponent.inject(com.example.glc.add.AddFragment)
You use Lazy on TwichHeaderRepository dependency in HeaderInterceptor's constructor to break the dependency cycle.
class HeaderInterceptor #Inject constructor(
private val lazyTwichHeaderRepository: Lazy<TwichHeaderRepository>
) {
// Access the dependency lazily with lazyTwichHeaderRepository.get()
}
I just needed to add #Named to resolve my problem and understand it as Mark said in the comment.
#Module
class AppModule {
#Singleton
#Provides
#Named("oauth2")
fun provideAccessTokenHttpClient() = OkHttpClient.Builder().build()
#Singleton
#Provides
fun provideRefreshTokenService(#Named("oauth2") client: OkHttpClient): RefreshTokenApi {
return Retrofit.Builder()
.baseUrl("https://id.twitch.tv/oauth2/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(RefreshTokenApi::class.java)
}
#Singleton
#Provides
#Named("igdb")
fun provideSearchDataHttpClient(headerInterceptor: HeaderInterceptor): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(headerInterceptor)
.build()
}
#Singleton
#Provides
fun provideRetrofit(#Named("igdb") client: OkHttpClient): TwichApi {
return Retrofit.Builder()
.baseUrl("https://api.igdb.com/v4/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(TwichApi::class.java)
}
}
I'm trying to inject the viewmodel to the Fragment using hilt, for that I created two modules one for my network component and another for the characters viewmodel.
The network module is installed in SingletonComponent and I need that the characters module installs in the FragmentComponent to get the Viewmodel through "by viewmodels()"
My activity and my fragment are annotated with "#AndroidEntryPoint" and my Application is annotated with "#HiltAndroidApp" my modules are:
#Module
#InstallIn(SingletonComponent::class)
class NetworkModule {
#Singleton
#Provides
fun provideLoggingInterceptor(): HttpLoggingInterceptor {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
return loggingInterceptor
}
#Singleton
#Provides
fun provideAuthorizationInterceptor(): AuthorizationInterceptor =
AuthorizationInterceptor()
#Singleton
#Provides
fun provideInterceptors(
loggingInterceptor: HttpLoggingInterceptor,
authorizationInterceptor: AuthorizationInterceptor
): HttpInterceptors = HttpInterceptors(
listOf(
loggingInterceptor,
authorizationInterceptor
)
)
#Singleton
#Provides
fun provideTimeouts(): HttpTimeouts = HttpTimeouts()
#Singleton
#Provides
fun provideHttpClient(
timeouts: HttpTimeouts,
interceptors: HttpInterceptors
): HttpBuilderFactory = HttpClientBuilderFactory(timeouts, interceptors)
#Singleton
#Provides
fun provideGsonConverter(): GsonFactory = GsonBuilderFactory()
#Singleton
#Provides
fun provideHttpServiceProvider(
httpBuilderFactory: HttpBuilderFactory,
gsonFactory: GsonFactory
): HttpProvider = HttpServiceProvider(httpBuilderFactory, gsonFactory)
}
#Module
#InstallIn(FragmentComponent::class)
class CharacterListModule {
#Singleton
#Provides
fun provideCharacterMapper(): Mapper<CharacterDataContainer, PaginatedCharacters> =
PaginatedCharactersMapper()
#Singleton
#Provides
fun provideCharacterServices(
httpServiceProvider: HttpProvider
): CharacterServices = httpServiceProvider.createService(CharacterServices::class.java)
#Singleton
#Provides
fun provideCharacterListRemoteSource(
characterServices: CharacterServices,
characterMapper: Mapper<CharacterDataContainer, PaginatedCharacters>
): CharacterListSource = CharacterListRemoteSourceImpl(
characterServices,
characterMapper
)
#Singleton
#Provides
fun provideCharacterListRepository(
characterListRemoteSource: CharacterListSource
): CharacterListRepository = CharacterListRepositoryImpl(
characterListRemoteSource
)
#Singleton
#Provides
fun provideCoroutineDispatcher() = Dispatchers.IO
#Singleton
#Provides
fun provideCharacterListUseCase(
coroutineDispatcher: CoroutineDispatcher,
characterListRepository: CharacterListRepository
) = CharacterListUseCase(
coroutineDispatcher,
characterListRepository
)
#Singleton
#Provides
fun provideCharacterUIMapper(): Mapper<Character, CharacterUI> = CharacterUIMapper()
}
and my viewmodel is:
#HiltViewModel
class CharacterListViewModel #Inject constructor(
private val characterListUseCase: CharacterListUseCase,
private val characterUIMapper: Mapper<Character, CharacterUI>
) : ViewModel() {
Also when I use SingletonComponent in both cases the application runs fine, but when try to use FragmentComponent fails:
Finally my dependencies are:
object Hilt {
internal object Versions {
const val hilt = "2.33-beta"
const val hiltViewModel = "1.0.0-alpha01"
}
const val hilt = "com.google.dagger:hilt-android:${Versions.hilt}"
const val hiltCompiler = "com.google.dagger:hilt-android-compiler:${Versions.hilt}"
const val hiltViewModel = "androidx.hilt:hilt-lifecycle-viewmodel:${Versions.hiltViewModel}"
const val hiltViewModelCompiler = "androidx.hilt:hilt-compiler:${Versions.hiltViewModel}"
}
As it names says "FragmentComponent"(s) only live as long as it fragments does. You can't inject something that is fragment scoped into a view model, because viewmodels outlive its fragments lifecycle.. Change "FragmentScoped" with "SingletonScoped".
Please read the official documentation first before working with scopes. 99% of the time, using a "SingletonComponent" is more than enough