Dagger2 ViewModel cannot be provided problem with #Binds ViewModelProvider factory - viewmodel

I have implemented the support for injection of ViewModels in complaience with
medium article
and googleRepo
unfortunately i get the error cannot be provided without an #Provides-annotated method. and i cant get to the bottom of it.
I have migrated the project to AndroidX. I have tried many answers available on StackOverflow without success.
AppComponent
#Singleton
#Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
MainActivityModule::class,
LoginActivityModule::class,
DispatcherActivityModule::class]
)
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(androidApplication: BeeApp)
}
AppModule
#Module(includes = [ViewModelModule::class])
class AppModule {
...(i provide non-related other object here)
}
ViewModelModule
#Suppress("unused")
#Module
abstract class ViewModelModule {
#Binds
#IntoMap
#ViewModelKey(UserProfileViewModel::class)
abstract fun bindUserViewModel(userProfileViewModel: UserProfileViewModel): ViewModel
#Binds
abstract fun bindViewModelFactory(viewModelFactory: DaggerViewModelFactory): ViewModelProvider.Factory
}
DaggerViewModelFactory
#Singleton
#Suppress("UNCHECKED_CAST")
class DaggerViewModelFactory #Inject constructor(private val viewModelsMap: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>) :
ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
val creator = viewModelsMap[modelClass] ?: viewModelsMap.asIterable().firstOrNull {
modelClass.isAssignableFrom(it.key)
}?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
return try {
creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
Fragment
class UserProfileFragment : BaseFragment(), Injectable {
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
lateinit var profileViewModel: UserProfileViewModel
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
profileViewModel = ViewModelProviders.of(this, viewModelFactory)
.get(UserProfileViewModel::class.java)
}
(...)
}
ViewModel
class UserProfileViewModel #Inject constructor(userAccountRepository: UserAccountRepository) : ViewModel() {
val userAccount: LiveData<Resource<UserAccount>> = userAccountRepository.loadUserAccount(true)
}
any help would be appreciated.

This is caused by an issue with Kotlin kapt on version 1.3.30 issue tracker
change to previous/newer version

Related

How to initialize repository from service in Springboot Kotlin

#Service
public class AvailablePolicyService {
#Autowired
private var availablePolicyRepository : AvailablePolicyRepository = **AvailablePolicyRepository()**
fun saveAvailablePolicy(availablePolicy: AvailablePolicy): AvailablePolicy { return availablePolicyRepository.save(availablePolicy) }
fun getAllAvailablePolicy(): List<AvailablePolicy>{ return availablePolicyRepository.findAll() }
fun getAvailablePolicyByPolicyId(policyId: String?): AvailablePolicy? {
var availablePolicies: List<AvailablePolicy> = getAllAvailablePolicy()
for (availablePolicy in availablePolicies) {
if (availablePolicy.getPolicyId().equals(policyId)) {
return availablePolicy
}
}
return null
}
fun getAvailablePolicyByPolicyCategory(policyCategory: String?): ArrayList<AvailablePolicy> {
var availablePolicies: List<AvailablePolicy> = getAllAvailablePolicy()
var availablePolicyCategory = ArrayList<AvailablePolicy>()
for (availablePolicy in availablePolicies) {
if (availablePolicy.getPolicyCategory().equals(policyCategory)) {
availablePolicyCategory.add(availablePolicy)
}
}
return availablePolicyCategory
}
}
#Repository
interface AvailablePolicyRepository : MongoRepository<AvailablePolicy, String>
The bolded text shows where the error is showing and it reads "Interface AvailablePolicyRepository does not have constructors". How do I initialize repository from service?
How do I initialize repository from service?
That's the thing. You don't! Spring does it for you:
#Autowired
private lateinit var availablePolicyRepository: AvailablePolicyRepository
Field injection is rather obsolete and you should consider using contructor injection instead.
#Service
class AvailablePolicyService(private val availablePolicyRepository: AvailablePolicyRepository) {...}

Kotlin: How to solve the type casting problem?

I am trying to save data to a database. To do this, I choose a service using enum.
interface MyService<K: Serializable, V: Serializable> {
fun gerResource(): JpaRepository<K, V>
fun convert(list: List<String>) : List<K>
fun saveData(data: List<K>) = gerResource().saveAll(data) // signature saveAll: <S extends T> List<S> saveAll(Iterable<S> var1)
}
class DbData : Serializable
class KafkaData : Serializable
#Service
class FirstService( val repository : JpaRepository<DbData, Long>) : MyService<DbData, Long> {
override fun gerResource() = repository
override fun convert(list: List<String>) : List<DbData>{
return listOf(DbData())
}
}
#Service
class SecondService(val repository : JpaRepository<KafkaData, Long>) : MyService<KafkaData, Long> {
override fun gerResource() = repository
override fun convert(list: List<String>) : List<KafkaData>{
return listOf(KafkaData())
}
}
enum class ServiceType {
FIRST,
SECOND
}
#Service
class Cast(
private val firstService: MyService<DbData, Long>,
private val secondService: MyService<KafkaData, Long>
){
private fun saveMessage(type: ServiceType, list: List<String>) {
val myService = when (type) {
ServiceType.FIRST -> firstService
ServiceType.SECOND -> secondService
}
val convertList = myService.convert(list)
myService.saveData(convertList)
}
}
But I have a problem with cast in the code
val convertList = myService.convert(list)
myService.saveData(convertList)
Kotlin thinks that convert returns List<Serialazable> and I get an error in saveData:
Type mismatch.
Required: List<Nothing>
Found: List<Serializable>
if I change fun saveData(data: List<K>) to fun saveData(data: List<Serializable>), I get an error in saveAll(data)
Type mismatch.
Required:K!
Found: Serializable!
Since saveAll is a Spring Framework method, I can't change it.
How can I fix the error?

How to mock repository so it doesn't instert or pull data from DB

So my issue is that in my SpringBoot REST application im testing my RestController. The problem is that i don't know how to mock the repository so it doesn't get or puts data into the DB. I'm using Kotlin and Mockk for mocking
Here is my Repository
#Repository
interface StandingOrderRepository: CrudRepository<StandingOrder, Int> {
fun findByNameAndVariableSymbol(name: String, variableSymbol: String): List<StandingOrder>
fun findByValidFromBetween(fromDate: String, toDate: String): List<StandingOrder>
fun findByValidFromAfter(fromDate: String) : List<StandingOrder>
}
And here is my Test
#SpringBootTest
#AutoConfigureMockMvc
internal class StandingOrderResourceTest {
#Autowired
lateinit var mockMvc: MockMvc
#Autowired
lateinit var objectMapper: ObjectMapper
private val standingOrderMapper = mockk<StandingOrderMapper>()
private val standingOrderRepository = mockk<StandingOrderRepository>()
private val standingOrderServiceImpl = mockk<StandingOrderServiceImpl>()
private val standingOrderResource = StandingOrderResource(standingOrderServiceImpl)
val baseUrl = "/api"
#Nested
#DisplayName("GetStandingOrders()")
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetStandingOrders {
#Test
fun `should return all StandingOrders`() {
standingOrderResource.getStandingOrders()
mockMvc.get(baseUrl)
.andDo { print() }
.andExpect {
status { isOk() }
content { contentType(MediaType.APPLICATION_JSON)}
}
//standingOrderResource.getStandingOrders() shouldBe listOf(standingOrderDto)
}
}
}
The problem is if i Make a API call or invoke the mocked repository it still gets actual data from DB
In your test code you should try to use method whenever() from org.mockito.kotlin for stubbing StandingOrderRepository's method call.
For example your code for stubbing will looks something like this
whenever(standingOrderRepository.findByNameAndVariableSymbol(any(),any())).thenReturn(listOf(StandingOrder(...)))
UPD: So you use Mockk, then you shuold use method every instead whenever from mockito.
So this is how i made it work maybe the issue was on my side how i was trying to use it #Anton Tokmakov was correct here is how i did it
#SpringBootTest
#AutoConfigureMockMvc
#ExtendWith(SpringExtension::class)
internal class StandingOrderResourceTest #Autowired constructor(
val mockMvc: MockMvc,
val objectMapper: ObjectMapper,
) {
#MockkBean
private lateinit var standingOrderResource: StandingOrderResource
#Nested
#DisplayName("GetStandingOrders()")
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetStandingOrders {
#Test
fun `should return all StandingOrders`() {
every { standingOrderResource.getStandingOrders() } returns
listOf(standingOrderDto1, standingOrderDto2)
mockMvc.get(baseUrl)
.andDo { print() }
.andExpect {
status { isOk() }
content { contentType(MediaType.APPLICATION_JSON)}
}
.andExpect {
jsonPath("\$..[0]", match(MockMvcResultMatchers.content().json(Gson().toJson(
listOf(
standingOrderDto1,
standingOrderDto2
)), false)))
}
}
}

Spring boot with testcontainers and jOOQ doesn't inject DSL context

I have some problem with spring boot + jOOQ and testcontainers. DSL context won't inject in my test class.
I made some preparations for using SQLContainer
class SpringTestContainer: PostgreSQLContainer<SpringTestContainer> {
private val postgreSqlPort = 5432
private val db = "m4"
companion object {
var instance: SpringTestContainer? = null
fun get(): SpringTestContainer {
if(instance == null) {
instance = SpringTestContainer()
}
return instance!!
}
}
override fun getDatabaseName(): String = db
constructor() : this("registry.dev.tskad.stdev.ru/m4/db:latest")
constructor(dockerImageName: String) : super(dockerImageName){
withImagePullPolicy(PullPolicy.alwaysPull())
addExposedPort(postgreSqlPort)
waitStrategy = LogMessageWaitStrategy()
.withRegEx(".*database system is ready to accept connections.*\\s")
.withTimes(1)
.withStartupTimeout(Duration.of(30, ChronoUnit.SECONDS))
}
override fun getJdbcUrl(): String {
return String.format("jdbc:postgresql://%s:%d/%s", containerIpAddress, getMappedPort(postgreSqlPort), databaseName)
}
override fun waitUntilContainerStarted() {
getWaitStrategy().waitUntilReady(this)
}
override fun getLivenessCheckPorts(): Set<Int?> {
return HashSet(getMappedPort(postgreSqlPort))
}
}
Then I created some abstraction for extending my integration test classes
#ContextConfiguration(initializers = [SpringIntegrationTest.Initializer::class])
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#JooqTest
abstract class SpringIntegrationTest {
#get:Rule
var postgreSQLContainer = SpringTestContainer.get()
inner class Initializer: ApplicationContextInitializer<ConfigurableApplicationContext> {
override fun initialize(applicationContext: ConfigurableApplicationContext) {
with(applicationContext.environment.systemProperties) {
put("spring.datasource.url", postgreSQLContainer.jdbcUrl)
put("spring.datasource.username", postgreSQLContainer.username)
put("spring.datasource.password", postgreSQLContainer.password)
}
}
}
}
and then I implemented test class
#ExtendWith(SpringExtension::class)
class TransactionRepositoryImplTest: SpringIntegrationTest() {
#Autowired
private var dslContext: DSLContext? = null
private var transactionRepository: TransactionRepository? = null
#Before
fun setUp() {
assertThat(dslContext).isNotNull
transactionRepository = TransactionRepositoryImpl(dslContext!!)
}
#After
fun tearDown() {
}
#Test
fun findTransactionData() {
transactionRepository?.findTransactionByVehicleUuid(null).apply {
assertNull(this)
}
}
}
and when I started tests of this class - tests are fails, because of assertions are not passed.
Here is the report of tests https://pastebin.com/0HeqDcCT
So.. how it impossible? I saw a few guides with this stack(Spring/jOOQ/TestContainers). And they are all working. Maybe I missed some test dependencies? If you have experience with this case - share your solution, please. I will be very grateful.
dependencies {
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.springframework.boot:spring-boot-starter-jooq")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.cloud:spring-cloud-starter-consul-config")
implementation("org.springframework.cloud:spring-cloud-stream")
implementation("org.springframework.cloud:spring-cloud-stream-binder-kafka")
implementation("org.springframework.kafka:spring-kafka")
implementation("org.springframework.boot:spring-boot-starter-amqp")
implementation ("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.10.3")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.10.3")
runtimeOnly("org.postgresql:postgresql:42.2.12")
jooqGeneratorRuntime("org.postgresql:postgresql:42.2.12")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
exclude(module = "junit")
}
testImplementation("com.ninja-squad:springmockk:2.0.1")
testImplementation("org.springframework.cloud:spring-cloud-stream-test-support")
testImplementation("org.springframework.kafka:spring-kafka-test")
testImplementation("org.springframework.amqp:spring-rabbit-test")
testImplementation("org.testcontainers:postgresql:1.14.3")
}
I might be missing something but in that setup, you have to manually run
postgreSQLContainer.start() somewhere. As an example it could be done in #BeforeAll.
I found the solution. The right way was to override the start method of test containers implementation and put in system properties the credentials to container DB.
Here is the working code:
class SpringTestContainer: PostgreSQLContainer<SpringTestContainer> {
private val postgreSqlPort = 5432
private val db = "m4"
companion object {
var instance: SpringTestContainer? = null
fun get(): SpringTestContainer {
if(instance == null) {
instance = SpringTestContainer()
}
return instance!!
}
}
override fun getDatabaseName(): String = db
constructor() : this("registry.dev.tskad.stdev.ru/m4/db:latest")
constructor(dockerImageName: String) : super(dockerImageName){
withImagePullPolicy(PullPolicy.alwaysPull())
addExposedPort(postgreSqlPort)
waitStrategy = LogMessageWaitStrategy()
.withRegEx(".*database system is ready to accept connections.*\\s")
.withTimes(1)
.withStartupTimeout(Duration.of(30, ChronoUnit.SECONDS))
}
override fun getJdbcUrl(): String {
return String.format("jdbc:postgresql://%s:%d/%s", containerIpAddress, getMappedPort(postgreSqlPort), databaseName)
}
override fun waitUntilContainerStarted() {
getWaitStrategy().waitUntilReady(this)
}
override fun getLivenessCheckPorts(): Set<Int?> {
return HashSet(getMappedPort(postgreSqlPort))
}
override fun start() {
super.start()
val container = get()
System.setProperty("DB_URL", container.jdbcUrl)
System.setProperty("DB_USERNAME", container.username)
System.setProperty("DB_PASSWORD", container.password)
}
}
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#SpringBootTest(properties = ["spring.cloud.consul.enabled = false"])
#EnableAutoConfiguration(exclude = [
RabbitAutoConfiguration::class,
KafkaAutoConfiguration::class
])
class TransactionRepositoryImplTest {
#get:Rule
var postgreSQLContainer = SpringTestContainer.get()
#Autowired
private lateinit var dslContext: DSLContext
#Autowired
private lateinit var transactionRepository: TransactionRepository
#MockkBean
private lateinit var connectionFactory: ConnectionFactory // this is the mock for rabbit connection. U may ignore it.
#Test
fun contextLoads() {
Assertions.assertNotNull(dslContext)
Assertions.assertNotNull(transactionRepository)
}
}
and then need to fix application.yml in the tests directory
spring:
datasource:
platform: postgres
url: ${DB_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
driverClassName: org.postgresql.Driver
in java it helps me:
on test put annotation. See JooqAutoConfiguration.class
#Import({ DataSourceAutoConfiguration.class,
TransactionAutoConfiguration.class, JooqAutoConfiguration.class})

How to initialize variables in parent abstract class of spring bean using Kotlin?

I have the next structure of spring beans
abstract class GenericRepository<T> {
private val FIND_BY_ID_SQL = "SELECT * FROM ${this.getTableName()} WHERE id = ?"
abstract fun getTableName(): String
abstract fun jdbcTemplate(): JdbcTemplate
abstract fun getMapper(): RowMapper<T>
fun find(id: Long): T? {
return jdbcTemplate().queryForObject(FIND_BY_ID_SQL, arrayOf(id), getMapper())
}
}
User repository
#Repository
class UserRepository(
#Autowired
private val jdbcTemplate: JdbcTemplate
) : GenericRepository<User>() {
companion object {
private const val INSERT_SQL = "INSERT INTO \"user\"(name, age) VALUES (?,?)"
}
private class LogMapper : RowMapper<User> {
override fun mapRow(rs: ResultSet, rowNum: Int): User? {
return User(
id = rs.getLong("id"),
name = rs.getString("name"),
age = rs.getInt("operation")
)
}
}
override fun getTableName(): String {
return "user"
}
override fun jdbcTemplate(): JdbcTemplate {
return jdbcTemplate
}
override fun getMapper(): RowMapper<User> {
return LogMapper()
}
}
The problem when Spring creates proxy and creates bean of UserRepository it doesn't initialize FIND_BY_ID_SQL leaving it null.
The question: how usign abstract class make spring initialize FIND_BY_ID_SQL variable?
UPD
I used #Component instead of #Repository and the problem was solved. FIND_BY_ID_SQL is not null anymore.
You could work around the problem by making it lazy:
private val FIND_BY_ID_SQL by lazy { "SELECT * FROM ${this.getTableName()} WHERE id = ?" }
However, you should first be sure it's an actual problem (e.g. that when you call find you get an exception), because the proxy might simply delegate to a "real" UserRepository with non-null FIND_BY_ID_SQL (and jdbcTemplate etc.), depending on Spring's internal details.
In addition, you need to be careful when your superclass properties are initialized depending on subclass; I think your exact situation should work, but I'd prefer to write it as
abstract class GenericRepository<T>(val tableName: String) {
private val FIND_BY_ID_SQL = "SELECT * FROM ${tableName} WHERE id = ?"
abstract val jdbcTemplate: JdbcTemplate
abstract val mapper: RowMapper<T>
fun find(id: Long): T? {
return jdbcTemplate.queryForObject(FIND_BY_ID_SQL, arrayOf(id), mapper)
}
}
#Repository
class UserRepository(
#Autowired
override val jdbcTemplate: JdbcTemplate
) : GenericRepository<User>("user") { ... }

Resources