Using koin dependency Injection library in Android. I have doubt in creating interface implementor.
My Interface is as below:
interface EnrollApiInterface {
#GET("person/templates/{id}")
fun yoonikEnroll(#Path("id") id: String) : Deferred<Response<ResponseBody>>
}
I am trying to create implementor as below:
class EnrollApiInterfaceImp() : EnrollApiInterface{
#GET("person/templates/{id}")
override fun yoonikEnroll(#Path("id") id: String) : Deferred<Response<ResponseBody>>{
return
}
}
But I don't know What I can return here?
It seems like you are using retrofit for your api interface, therefore you have to use the retrofit builder:
Retrofit
.Builder()
.baseUrl("https://theurl.com")
.build()
.create(EnrollApiInterface::class.java)
See the retrofit documentation for more information.
Related
how can I make Dto map and pass it to the use-case using clean architecture in android kotlin
i tried to make an object class and pass it to the use-case but it didn't work
Note that I will use HILT for dependency injection ... you can use any di library.
You can make a base interface mapper for objects like this:
interface mapper <T,R> {
fun map(t: T): R
}
Then create a class that implements Mapper interface:
class ProductMapper #Inject constructor() : Mapper<RemoteProduct, ProductModel>() {
override fun map(t: RemoteProduct): ProductModel {
// Pass any argument you want or do some business logic
return ProductModel(t.id, t.name)
}
Now I Suggest injecting this ProductMapper inside the repo when you get the response from API or locale database then return mapped data to the use-case.
But anyway you can inject this mapper inside use-case like this:
class GetProductsUseCase #Inject constructor (private val productsMapper: ProductsMapper) {
// Don't forget to inject your repo too
operator fun invoke(): ProductMode {
return productsMapper.map(repo.getProduct())
}
}
Finally using Hilt create new module to bind ProductMapper in our case there is no need to do that step but if your mapper depends on another classes you need to add it in module like this:
#Module
#InstallIn(SingltonComponent::Class)
interface AppModule {
#Binds
fun bindProductDetailsMapper(productsMapper: ProductsMapper): Mapper
}
I have a sealed class like below,
sealed class ApiResult<T>(
val data: T? = null,
val errors: List<Error>? = null
) {
class Success<T>(data: T?) : ApiResult<T>(data)
class Failure<T>(
errors: List<Error>,
data: T? = null
) : ApiResult<T>(data, errors)
}
And this is my Retrofit API interface,
interface Api {
#POST("login")
suspend fun login(#Body loginRequestDto: LoginRequestDto): ApiResult<LoginResponseDto>
}
What I want to achieve is, internally wrap the LoginResponseDto in BaseResponseDto which has the success, error, and data fields. Then put them in the ApiResult class.
data class BaseResponseDto<T>(
val success: Boolean,
val errors: List<Int>,
val data: T?
)
In this case, LoginResponseDto is my data class. So, Retrofit should internally wrap the LoginResponseDto in BaseResponseDto then I will create the ApiResult response in my custom call adapter. How can I tell Retrofit to internally wrap this? I don't want to include the BaseResponseDto in the API interface every time.
After spending the whole day, I could achieve it. I had to create a custom converter. Here it is,
class ApiConverter private constructor(
private val gson: Gson
) : Converter.Factory() {
override fun responseBodyConverter(
type: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): Converter<ResponseBody, *> {
val baseResponseType = TypeToken.get(BaseResponseDto::class.java).type
val finalType = TypeToken.getParameterized(baseResponseType, type)
return Converter<ResponseBody, Any> { value ->
val baseResponse: BaseResponseDto<*> =
gson.fromJson(value.charStream(), finalType.type)
baseResponse
}
}
companion object {
fun create(gson: Gson) = ApiConverter(gson)
}
}
Now I don't have to specify the BaseResponseDto every time in the API interface for retrofit.
Great question!
If you want to implement your own solution you would need a custom Retrofit CallAdapter.
My recommendation would be Sandwich an Open source Library which does the exact same thing.
If you want to implement your own soultion here's one way
Modeling Retrofit Responses With Sealed Classes and Coroutines
I have just learnt manual dependency injection, but I am trying out Hilt to handle these dependency injections.
I want to inject a ViewModel into a Fragment. The fragment is contained within an Activity. Right now, I have added the annotations to Application, Activity, and Fragment.
#HiltAndroidApp
class MovieCatalogueApplication : Application()
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
...
}
#AndroidEntryPoint
class HomeFragment : Fragment() {
private lateinit var binding: FragHomeBinding
private val viewmodel: HomeViewModel by viewModels()
...
As can be seen, my HomeFragment depends on HomeViewModel. I have added a ViewModel injection as described here like so.
class HomeViewModel #ViewModelInject constructor(
private val movieRepository: MovieRepository,
private val showRepository: ShowRepository,
#Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
...
}
However, the ViewModel requires two repositories. Right now, my MovieRepository is like so.
class MovieRepository (private val movieApi: MovieService) {
...
}
In the above code, MovieService will be created by Retrofit using the Retrofit.create(class) method. The interface used to create MovieService is like so.
interface MovieService {
...
}
To get my Retrofit instance, I am using the following code.
object RetrofitService {
...
private var _retrofit: Retrofit? = null
val retrofit: Retrofit
get() {
return when (_retrofit) {
null -> {
_retrofit = Retrofit.Builder()
.client(client)
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
_retrofit!!
}
else -> _retrofit!!
}
}
}
I am not too sure how I can inject the Retrofit into the Repository to be used by my ViewModel later on. Could someone give me some pointers or step-by-step instructions on how to do this?
Apparently, it is not as hard as it seems.
You have to first define the binding information to Hilt. Binding information tells Hilt how to provide the instances of the dependency specified. Because MovieService is created using a Retrofit (which is a 3rd-party class not created by yourself) using the builder pattern, you can't use the constructor injection and you have to instead use Hilt modules and the annotation #Provides to tell Hilt about this binding information.
As described in the doc, the annotated function in the Hilt module you have created will supply the following information to Hilt so that Hilt can provide the instances of the dependency.
• The function return type tells Hilt what type the function provides instances of.
• The function parameters tell Hilt the dependencies of the corresponding type.
• The function body tells Hilt how to provide an instance of the corresponding type. Hilt executes the function body every time it needs to provide an instance of that type.
In the end, you only need to modify the MovieRepository class, add a module for each repository, and annotate the function that tells Hilt how to provide the service instance created with Retrofit with #Provides.
Code.
class MovieRepository #Inject constructor(
private val movieApi: MovieService
) {
...
}
interface MovieService {
...
}
#Module
#InstallIn(ActivityRetainedComponent::class)
object MovieModule {
#Provides
fun provideMovieService(): MovieService
= RetrofitService.retrofit.create(MovieService::class.java)
}
As you can see, the ActivityRetainedComponent is referred in the #InstallIn annotation because the Repository is to be injected to a ViewModel. Each Android component is associated to different Hilt components.
I'm new in dagger , I want to inject context and network (using retrofit) in my classes .
this is my code so far :
#Module
// Safe here as we are dealing with a Dagger 2 module
#Suppress("unused")
object NetworkModule {
#Provides
#Reusable
#JvmStatic
internal fun provideMainApi(retrofit: Retrofit): MainApi {
return retrofit.create(MainApi::class.java)
}
#Provides
#Reusable
#JvmStatic
internal fun provideRetrofitInterface(): Retrofit {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val client = OkHttpClient.Builder().addInterceptor(interceptor).build()
return Retrofit.Builder()
.baseUrl(Constants.baseUrl)
.addConverterFactory(MoshiConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.client(client)
.build()
}
}
#Module
class AppModule(private val app: Application) {
#Provides
#Singleton
fun provideApplication() = app
}
this is my component :
#Singleton
#dagger.Component(modules = arrayOf(AppModule::class, NetworkModule::class))
interface AppComponent {
///for injecting retrofit network
fun injectMain(mainRepository: MainRepository)
#dagger.Component.Builder
interface Builder {
fun build(): AppComponent
fun networkModule(networkModule: NetworkModule): Builder
fun appModule(appModule: AppModule):Builder
}
}
I want to use it in my repository , I've a baseRepository :
open class BaseRepository {
private val injector: AppComponent = DaggerAppComponent
.builder()
.networkModule(NetworkModule)
.build()
init {
inject()
}
private fun inject() {
when (this) {
is MainRepository -> injector.injectMain(this)
}
}
}
when I run the app , I get this error "module.AppModule must be set"
I understand the error and I should provie appMOdule in my base repository but the problem is I don't have any application or context in base repository
how should I fix this ?
the second problem I've is this , I've heard that I should once make the dagger and use it in my entire app and I shouldn't make it every time , it means I should use application for that .
but how can I use injector in an application class , it doesn't make sense
If I were you I would go through this project and see there how everything is done.
https://github.com/google/iosched
Here is explained what this project is about:
https://medium.com/androiddevelopers/google-i-o-2018-app-architecture-and-testing-f546e37fc7eb
Also you can check the Dagger branch ot this projec:
https://github.com/android/architecture-samples
It was all done by Jose Alcerreca and some other guys. These are the guidelines of Google about Android and there will be the answer of most of your questions.
but how can I use injector in an application class , it doesn't make sense There is a DaggerApplication class which you can extend and inject your App class however you want.
And I am telling you all of this, because I think you have learned Dagger from old sources. I would have changed a lot of stuff in your code.
Also I am using Dagger since 1.5 years and I have never ever written such a code like the one in: BaseRepository. I use only and only #Inject annotations and that is all throuhg my classes which are not Dagger related. Creating a Component in a Repository? -> Never!
This is how your classes should end up everywhere.
class Repostory #Inject constructor(
private val dependency1: Dependency1
) {}
class Activity {
#Inject lateinit var dependency2: Dependency2
}
I'm new to Dagger and have the following setup:
// data models
open class BaseEntity (open val id: Long)
data class UserEntity (override val id: Long, val name: String) : BaseEntity(id)
data class FruitEntity (override val id: Long, val status: String) : BaseEntity(id)
// interface to spec common API response operations
interface Repo<T> {
fun getEntities(): List<T>
}
// entity specific implementation of the repo interface
class RepoImpl<T: BaseEntity> #Inject constructor(apiService: ApiService) : Repo<T> {
override fun getEntities(): List<T> {
val entities = apiService.getEntities(...)// get result from respective entity network service, e.g users, fruits etc
return entities
}
}
// DI: provide entity-specific implementations of the interface
#Singleton
#Provides
fun provideUserRepoImpl(userService: UserApiService): RepoImpl<BaseEntity> {
return RepoImpl(userService)
}
#Singleton
#Provides
fun provideFruitRepoImpl(fruitService: FruitApiService): RepoImpl<BaseEntity> {
return RepoImpl(fruitService)
}
When I build the project, this error comes up:
error: RepoImpl<com.example.data.model.BaseEntity> is bound multiple times
...
#org.jetbrains.annotations.NotNull #Provides #Singleton com.example.data.source.remote.RepoImpl<com.example.data.model.BaseEntity> com.example.app.di.AppModule.provideUserRepoImpl()
#org.jetbrains.annotations.NotNull #Provides #Singleton com.example.data.source.remote.RepoImpl<com.example.data.model.BaseEntity> com.example.app.di.AppModule.provideFruitRepoImpl()
And when I try to provide the entity-specific instances, like this:
#Singleton
#Provides
fun provideUserRepoImpl(userService: UserApiService): RepoImpl<UserEntity> {
return RepoImpl(userService)
}
#Singleton
#Provides
fun provideFruitRepoImpl(fruitService: FruitApiService): RepoImpl<FruitEntity> {
return RepoImpl(fruitService)
}
I get the following:
error: RepoImpl<com.example.data.model.BaseEntity> cannot be provided without an #Inject constructor or from an #Provides-annotated method
Also I have tried separating the provider methods of fruits & users into their respective modules but latter error also comes up, maybe I'm not hooking up the FruitModule, UserModule and AppModule correctly. I'm having similar problems providing implementations of interfaces.
What would be a correct approach here, or is it not at all possible to inject parametrized classes with Dagger?
UPDATE
I have managed to solve the dependency issue on RepoImpl by explicitly providing each object to be injected (the latter option where we provide RepoImpl<UserEntity> and RepoImpl<FruitEntity>). I think the #Inject annotation on the constructor was somehow not being registered,
so had to refresh the project.
However, I cannot figure out how to provide implementations (or sub-types) of parametrized interfaces just yet.
Consider for example the ApiService parameter of RepoImpl defined as follows:
// base service for all types of items
interface ApiService<T: BaseAttributes> {
fun getEntities(): T
}
// service impl for fruits
class FruitService : ApiService<FruitAttributes> {
override fun getEntities(): FruitResponses {...}
}
// service impl for users
class UserService : ApiService<UserAttributes> {
override fun getEntities(): UserResponses {...}
}
// AppModule.kt:
#Singleton
#Provides
fun provideUserService() : ApiService<UserAttributes> {
return UserService()
}
#Singleton
#Provides
fun provideFruitService() : ApiService<FruitAttributes> {
return FruitService()
}
error: "... ApiService<BaseAttributes> cannot be provided without an #Provides-annotated method ..."
whereas
...
fun provideUserService() : ApiService<BaseAttributes> {
return UserService() as ApiService<BaseAttributes>
}
...
fun provideFruitService() : ApiService<FruitAttributes> {
return FruitService() as ApiService<BaseAttributes>
}
error: "... ApiService<BaseAttributes> is bound multiple times: ..."
How else can I inject these implementations of the interface?
Turns out there's a well-discussed wildcard issue on dagger/kotlin generics. Particularly, the "cannot be provided without an #Provides-annotated method" error on parametrized classes requires suppressing generation of wildcard types (covariants or contravariants) with the #JvmSuppressWildcard annotation at the injection site. So I would have done:
class RepoImpl<T: BaseEntity> #Inject constructor(apiService: #kotlin.jvm.JvmSuppressWildcards ApiService) : Repo<T> { ... }
Though I actually ended up converting RepoImpl into an abstract class and creating concrete ones for Fruit & User types and providing them at the module-level instead:
class UserRepoImpl #Inject constructor(apiService: UserService) : RemoteRepoImpl(apiService)
class FruitRepoImpl #Inject constructor(apiService: FruitService) : RemoteRepoImpl(apiService)
This related SO question contains another example.
Finally, this Kotlin+Dagger thread contains some nice gotchas and tips relevant to this problem