I use my own custom adapter classes, but when I want to provide them, I have a problem and I do not understand why!
#Module
abstract class AppointmentListModule {
companion object {
#Provides
#PerChildFragment
fun adapter(appExecutors: AppExecutors): SingleDataAdapter<AppointmentDigestData> = SingleDataAdapter(
appExecutors,
R.layout.item_appointment_patient,
SimpleDiffCallback(AppointmentDigestData::id),
BR.item
)
}
}
This is the error I receive:
error: #Provides methods can only be present within a #Module or #ProducerModule
public final ir.logicfan.core.ui.recyclerview.adapter.SingleDataAdapter<com.ennings.data.entity.patient.AppointmentDigestData> adapter(#org.jetbrains.annotations.NotNull()
SingleDataAdapter constructor:
open class SingleDataAdapter<T>(
appExecutors: AppExecutors,
#LayoutRes protected val itemLayout: Int,
diffCallback: DiffUtil.ItemCallback<T>,
private val bindingItemVariableId: Int,
private val positionBindingVariableId: Int? = null
) : ListAdapter<T, DataBindingViewHolder<T>>(
AsyncDifferConfig.Builder<T>(diffCallback)
.setBackgroundThreadExecutor(appExecutors.diskIO())
.build()
) {....}
Anyone have an idea?
I cannot check it right now, but as it stated here try to add #JvmStatic to the fun adapter(appExecutors: AppExecutors) function. And if you can, please show the SingleDataAdapter constructor
Related
I have an interface PaymentProvider:
interface PaymentProvider {
fun pay(amount: Double): Boolean
}
Here is different implementations:
class PayPalPayment #AssistedInject constructor(
private val payPalRepo: PayPalRepository,
#Assisted private val paymentData: PaymentData
): PaymentProvider {
#AssistedFactory
interface Factory : PaymentProviderFactory
override fun pay(amount: Double): Boolean {
// some code
}
}
class CreditCardPayment(
private val creditCardPaymentRepo: CreditCardPaymentRepository
): PaymentProvider {
override fun pay(amount: Double): Boolean {
// some code
}
}
Also I have PaymentManager class who should receive PaymentProvider.
class PaymentManager(
private val paymentProvider:PaymentProvider
) {
// some logic
}
I want this class not to know which specific implementation of the PaymentProvider interface it works with. For this I'm provide with dagger implementation using multibindings.
Here is my code:
#Binds
#[IntoMap PaymentProviderKey(Keys.PAYPAL)]
fun bindPayPalPayment (factory: PayPalPayment.Factory): PaymentProviderFactory
#Binds
#[IntoMap PaymentProviderKey(Keys.CREDIT_CARD)]
fun bindCreditCardPayment (factory: CreditCardPayment.Factory): PaymentProviderFactory
interface PaymentProviderFactory{
fun create(key: PaymentProviderKey, paymentData: PaymentData): PaymentProvider
}
Now I have a map of factories, but I don't understand how to provide specific implementation of interface. I tried to provide PaymentProviderFactory:
#Provides
fun providePaymentManager(
factory: PaymentProviderFactory
): PaymentManager = PaymentManager (
factory
)
and i get this kind of error:
Invalid return type: PaymentProvider. An assisted factory's abstract
method must return a type with an #AssistedInject-annotated
constructor.
Please, help me. I have a problem when using #AssistedInject, without it I provide map of PaymentProvider and then I provide factory to create concrete implementation:
#Provides
fun providePaymentProviderFactory(
paymentProviders: Map<PaymentProviderKey, #JvmSuppressWildcards Provider<PaymentProvider>>
): PaymentProviderFactory=
object : PaymentProviderFactory{
override fun create(key: PaymentProviderKey): PaymentProvider? {
val paymentProvider = paymentProviders[key]
return paymentProvider?.get()
}
}
In this case everything works, however I need to use #AssistedInject because I need to pass the data that I receive at runtime.
I got an error
public abstract static class SingletonC implements FragmentGetContextFix.FragmentGetContextFixEntryPoint,
^
kotlin.jvm.functions.Function1<? super pl.beskidmedia.bm.viewModel.repozytory.model.Token,kotlin.Unit> is injected at
pl.beskidmedia.bm.domainlayer.viewmodel.tv.TvViewModel(fetchHlsUseCase, �)
pl.beskidmedia.bm.domainlayer.viewmodel.tv.TvViewModel is injected at
pl.beskidmedia.bm.domainlayer.viewmodel.tv.TvViewModel_HiltModules.BindsModule.binds(vm)
#dagger.hilt.android.internal.lifecycle.HiltViewModelMap java.util.Map<java.lang.String,javax.inject.Provider<androidx.lifecycle.ViewModel>> is requested at
dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.ViewModelFactoriesEntryPoint.getHiltViewModelMap() [pl.beskidmedia.bm.MainApplication_HiltComponents.SingletonC ? pl.beskidmedia.bm.MainApplication_HiltComponents.ActivityRetainedC ? pl.beskidmedia.bm.MainApplication_HiltComponents.ViewModelC]
when i tried to provide my use-case to view model, here are my functions that should create use case invoke() instance that i want to use later in my view model
fun getHls(
tvRepository: TvRepository
): Flow<List<Hls>> {
return tvRepository.getHls()
}
typealias GetHlsUseCase = () -> Flow<List<Hls>>
fun fetchHls(
tvRepository: TvRepository,
token: Token
) {
return tvRepository.fetchHls(token.token)
}
typealias FetchHlsUseCase = (Token) -> Unit
and here are my modules
#Module
#InstallIn(ViewModelComponent::class)
class ProvideFetchHlsUseCase {
#Provides
fun provideFetchHlsUseCase(
tvRepository: TvRepository
): FetchHlsUseCase = { fetchHls(tvRepository, it) }
}
#Module
#InstallIn(ViewModelComponent::class)
class ProvideGetHlsUseCase {
#Provides
fun provideGetHlsUseCase(
tvRepository: TvRepository
): GetHlsUseCase = { getHls(tvRepository) }
}
and finally my view model
#HiltViewModel
class TvViewModel #Inject constructor(
private val fetchHlsUseCase: FetchHlsUseCase,
private val getHlsUseCase: GetHlsUseCase
) : BaseViewModel() {
val hls = getHlsUseCase().asLiveData(coroutineExceptionHandler)
}
I can't change my provide to binding (this is what i saw in different questions here) or at least I don't know how to do it. When I was implementing this into my project I was inspired by https://medium.com/swlh/functional-use-cases-f896f92e768f this article, but author show only how to implement view model (no interface or bindings). From what i understands dagger should use designated provide methods that I implemented in modules but for some reason it doesn't happen.
Ok so this was a tricky one cuz this problem is actually well known but it is hard to google if you don't know how to name it, this was resolved here
In short all I needed to do was insert #JvmSuppressWildcards behind my typealias "classes" in ViewModel at the insertion point like so
#HiltViewModel
class TvViewModel #Inject constructor(
private val fetchHlsUseCase: #JvmSuppressWildcards FetchHlsUseCase,
private val getHlsUseCase: #JvmSuppressWildcards GetHlsUseCase
) : BaseViewModel() {
putting this annotation behind private keyword did nothing that why i had problems with this :D
here I am trying to inject the adapter in activity via field injection. Adapter has a parameter(list).
Can somebody assist me here? i am facing compile time error
cannot be provided without an #Provides-annotated method.
Please refer below code
#AndroidEntryPoint
class RecipeActivity() : PostLoginActivity() {
var TAG = MainActivity::class.java.simpleName
private lateinit var binding: ActivityRecipeBinding
private val viewModel: RecipeViewModel by viewModels()
#Inject lateinit var adapter: RecipeAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
class RecipeAdapter #Inject constructor(list: MutableList<RecipeModel> ) :
BaseAdapter<RecipeModel>(list) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<RecipeModel> {
return RecipeViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_recipe, parent, false), this
)
}
override fun onBindViewHolder(holder: BaseViewHolder<RecipeModel>, position: Int) {
holder.bindData(baseList[position])
}
}
data class RecipeModel(
var title: String,
var imageType: String,
var url: String
) : Item()
In order to Inject a class, Hilt/Dagger needs to understand exactly how to Inject it. In your project, you should have a 'Module' object. Within here, you can create #Provides methods, which tell Hilt/Dagger exactly what a class looks like so that it can be injected (find out more here).
For example, to provide a class that implements some Android Retrofit services, you might have a module that looks like:
#Module
#InstallIn(ActivityComponent::class)
object AnalyticsModule {
#Provides
fun provideAnalyticsService(
// Potential dependencies of this type
): AnalyticsService {
return Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(AnalyticsService::class.java)
}
}
In this example, we can now use #Inject for an AnalyticsService, as Hilt/Dagger now knows how to make one!
In your scenario, it looks like your adapter needs to be constructed with a list of RecipeModels. As you will unlikely have access to this data at the Module level, I don't think you want to be injecting the Adapter like this? Simply creating it in the Activity should be sufficient for what you need!
Something like this:
private var adapter: RecipeAdapter? = null // OR
lateinit var adapter: RecipeAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = RecipeAdapter(viewModel.recipeModels)
}
As a rule of thumb, it is generally more common to use injection for services, factories and view models rather than UI elements like adapters, as these UI elements need to often be constructed with actual data which isn't available in an application's Hilt/Dagger module.
Well as an error suggests you need to have a module in which you provide default construction of you adapter.
Example:
#Module
#InstallIn(ActivityComponent::class)
object AppModule {
#Provides
fun provideRecipeAdapter(
list: MutableList<RecipeModel>
): RecipeAdapter {
return RecipeAdapter(list)
}
}
This is just an example of what you are missing, not actual working code. For more details of how to create these modules look at the documentation
good day, i'm trying to create custom component in hilt so i followed this tutorial medium and this tutorial as well documentation.
but when i run the application i get this error indicating that it failed to create the viewholdermanager
kotlin.UninitializedPropertyAccessException: lateinit property viewHolderManager has not been initialized.
so i believe that i'm missing something as per my understanding "We use the provider Dagger generates for us to create instances of ViewHolderComponent( custom component)"
really appreciate your help. thanks in advance
here is what i done :-
#Scope
#MustBeDocumented
#Retention(value = AnnotationRetention.RUNTIME)
annotation class ViewHolderScope
#ViewHolderScope
#DefineComponent(parent = ApplicationComponent::class)
interface ViewHolderComponent{
#DefineComponent.Builder
interface Builder {
fun build(): ViewHolderComponent
fun viewHolder(#BindsInstance viewHolder: RecyclerView.ViewHolder?): ViewHolderComponent.Builder?
}
}
#Singleton
class ViewHolderManager #Inject constructor(
private val viewHolderComponentProvider: Provider<ViewHolderComponent.Builder>
) {
var viewHolderComponent: ViewHolderComponent? = null
private set
fun setViewHolder(viewHolder: RecyclerView.ViewHolder) {
viewHolderComponent = viewHolderComponentProvider.get().viewHolder(viewHolder)?.build()
}
}
#Module
#InstallIn(ViewHolderComponent::class)
class ViewHolderModule{
#Provides
#ViewHolderScope
fun provideLifecycleRegistry(viewHolder: RecyclerView.ViewHolder): LifecycleRegistry = LifecycleRegistry(viewHolder)
#Provides
#ViewHolderScope
fun provideArrayAdapter(viewHolder: RecyclerView.ViewHolder): ArrayAdapter<DataItem> = ArrayAdapter((viewHolder as BaseItemViewHolder<*, *>).parent.context, R.layout.item_dropdown_menu_popup,
ArrayList())
}
class SavedCarItemViewHolder(): RecyclerView.ViewHolder(...){
#EntryPoint
#InstallIn(ViewHolderComponent::class)
interface ViewHolderEntryPoint {
fun getLifecycleRegistry(): LifecycleRegistry
fun getDataItemArrayAdapter(): ArrayAdapter<DataItem>
}
protected fun onCreate() {
injectDependencies()
}
lateinit var lifecycleRegistry: LifecycleRegistry
#Inject lateinit var viewHolderManager:ViewHolderManager
fun injectDependencies() {
val hiltEntryPoint = EntryPoints.get(viewHolderManager.viewHolderComponent, ViewHolderEntryPoint::class.java)
lifecycleRegistry = hiltEntryPoint.getLifecycleRegistry()
}
}
I think you have to change your class ViewHolderModule to object ViewHolderModule otherwise #Provides is not executed. Please share your experience with this result and when it does not work, I will try to dig deeper in.
You should use #Inject annotation to constructor and inject ViewHolderManager
I got some issues with Kotlin when translating my android project from java to Kotlin.
Say i have interface I and interface O which extends interface I.
interface I{
}
interface O: I{
}
And generic class A which have generic parameter V that extends interfaceI, and generic class B which extends class A:
abstract class A<V: I> {
}
class B : A<O>() {
}
When i'm trying to create such property:
val returnB: A<I>
get() = b
I'm getting compiler error 'required A, found B'. In Java this will work without any issues. How can i access this using Kotlin ?
I need to use this approach for Basic classes in my application.
BaseViewModel which have generic parameter for Navigator class:
abstract class BaseViewModel<N>(application: Application, val repositoryProvider:
RepositoryProvider) : AndroidViewModel(application) {
var navigator: N? = null
fun onDestroyView() {
navigator = null
}
open fun onViewAttached() {
}
}
BaseActivity class:
abstract class BaseActivity<T : ViewDataBinding, V : BaseViewModel<BaseNavigator>> : AppCompatActivity(),
BaseFragment.Callback, BaseNavigator {
// .......
private var mViewModel: V? = null
/**
* Override for set view model
* #return view model instance
*/
abstract val viewModel: V
// .......
}
BaseNavigator interface uses for VM - View communication:
interface BaseNavigator {
fun invokeIntent(intent: Intent?, b: Bundle?, c: Class<*>?,
forResult: Boolean, requestCode: Int)
fun replaceFragment(fragment: Fragment, addToBackStack: Boolean)
fun showDialogFragment(fragment: DialogFragment?, tag: String?)
fun showToast(message: String?)
}
Here example code where i'm extending these classes:
AuthViewModel:
class AuthViewModel(context: Application, repositoryProvider: RepositoryProvider) :
BaseViewModel<AuthNavigator>(context,repositoryProvider) {
// ....
}
AuthNavigator:
interface AuthNavigator : BaseNavigator {
fun requestGoogleAuth(code: Int)
fun requestFacebookAuth(callback: FacebookCallback<LoginResult>)
}
And AuthActivity class where error was appeared:
class AuthActivity : BaseActivity<ActivityAuthBinding, BaseViewModel<BaseNavagator>>(),
GoogleApiClient.OnConnectionFailedListener, AuthNavigator {
#Inject
lateinit var mViewModel: AuthViewModel
override val viewModel: BaseViewModel<BaseNavigator>
get() = mViewModel // Required:BaseViewModel<BaseNavigator> Found: AuthViewModel
}
I'm also tried to change generic parameter in AuthActivity from BaseViewModel to AuthViewModel, but compiler throws error 'required BaseViewModel'.
And i tried to change
override val viewModel: BaseViewModel<BaseNavigator>
get() = mViewModel
to
override val viewModel: AuthViewModel
get() = mViewModel
but in this case compiler throws error 'Property type is 'AuthViewModel', which is not a subtype type of overridden'.
update:
That works when i add out property to BaseViewModel:
BaseViewModel<out N : BaseNavigator>
But in this case i can only create
private var navigator: N? = null
which i need to be public so i can set it in the Activity class. Can i create public setter for this property? When i'm trying to create setter an error occurs:
private var navigator: N? = null
fun setNavigator(n: N) { // error: Type parameter N is declared as 'out' but occurs in 'in' position in type N
navigator = n
}
It looks like you are expecting the type parameter to behave covariantly. Kotlin uses declaration-site variance. If you do not specify the variance, generic type parameters are invariant.
In other words, right now there is no relationship between A<I> and A<O>. But if you declare
abstract class A<out V : I>
then A<O> is a subtype of A<I>.
(There is also <in> for contravariance, which works the other way around. See https://kotlinlang.org/docs/reference/generics.html for more details.)