Kotlin lateinit not working with #Inject annotation - kotlin

I am new to dagger 2 and kotlin both. Getting lateinit property not initialized.
I have a module which have few #Provides methods but one of the class not able to create object which used #Inject and lateinit.
Login service takes "LoginAPI" as parameter and works fine but as i want all my login related API's to use the same service. There is one more API related "LoginWithOrgAPI".
Now my need is to get any API object when needed in the LoginService class. So i tries using lateinit with #Inject as show in LoginService class but its not working.
#Module(includes = [(NetworkingModule::class)])
class LoginModule {
#Provides
fun provideLoginApi(retrofit: Retrofit): LoginApi =
retrofit.create(LoginApi::class.java)
#Provides
fun provideLoginWithOrgApi(retrofit: Retrofit): LoginWithOrgApi =
retrofit.create(LoginWithOrgApi::class.java)
#Provides
fun provideLoginService(api: LoginApi): LoginService =
LoginService(api)
#Provides
fun provideLoginInteractor(apiService: LoginService): LoginInteractor =
LoginInteractor(apiService)
}
// adding LoginService class
class LoginService(val loginAPI: LoginApi) {
#Inject
lateinit var loginWithOrgApi: LoginWithOrgApi
fun loginAPIService(user: String, password: String?, extension: String, otp: String?,
hardwareId: String): Single<LoginAPIResponseData> {
password?.let {
return loginAPI.getLogin(user, it, extension, null, hardwareId)
}?: run {
return loginAPI.getLogin(user, null, extension, otp, hardwareId)
}
}
fun loginWithOrg(user: String, password: String?, extension: String, otp: String?,
userId: String, hardwareId: String): Single<LoginAPIResponseData>{
password?.let {
return loginWithOrgApi.getLogin(user, it, extension, null, userId, hardwareId)
}?: run {
return loginWithOrgApi.getLogin(user, null, extension, otp, userId, hardwareId)
}
}
}
// component
#Component(modules = [(LoginModule::class)])
interface LoginComponent {
fun loginInteractor(): LoginInteractor
}
// api interface
interface LoginWithOrgApi {
#POST("access/v1/login/")
#FormUrlEncoded
fun getLogin(#Field("user") user: String,
#Field("password") password: String?,
#Field("mobile_extension") extension: String,
#Field("otp") otp: String?,
#Field("user_id") userId: String,
#Field("hardware_id") hardwareId: String): Single<LoginAPIResponseData>
}
Getting the crash saying "lateinit" property not initialized when trying to call method "loginWithOrg"
My understanding is that once define and provided through module, i can get the object through #Inject in the dependency graph but something is missing here.
// my objective for LoginService class
class LoginService() {
#Inject
var loginWithOrgApi: LoginWithOrgApi
#Inject
var loginApi: LoginApi
fun loginAPIService(user: String, password: String?, extension: String, otp: String?,
hardwareId: String): Single<LoginAPIResponseData> {
password?.let {
return loginAPI.getLogin(user, it, extension, null, hardwareId)
}?: run {
return loginAPI.getLogin(user, null, extension, otp, hardwareId)
}
}
fun loginWithOrg(user: String, password: String?, extension: String, otp: String?,
userId: String, hardwareId: String): Single<LoginAPIResponseData>{
password?.let {
return loginWithOrgApi.getLogin(user, it, extension, null, userId, hardwareId)
}?: run {
return loginWithOrgApi.getLogin(user, null, extension, otp, userId, hardwareId)
}
}
}

Because you are mixing two independent things: member injection via void inject(MyClass myClass); and constructor injection.
In fact, you even have a separate field that "ought to be member-injected", even though you could technically receive that as a constructor param?
class LoginService(val loginAPI: LoginApi) { // <-- constructor param + field
#Inject
lateinit var loginWithOrgApi: LoginWithOrgApi // <-- why is this a lateinit member injected field?
// this could also be constructor param
So it should be as follows.
#Module(includes = [(NetworkingModule::class)])
class LoginModule {
#Provides
fun loginApi(retrofit: Retrofit): LoginApi =
retrofit.create(LoginApi::class.java)
#Provides
fun loginWithOrgApi(retrofit: Retrofit): LoginWithOrgApi =
retrofit.create(LoginWithOrgApi::class.java)
//#Provides
//fun provideLoginService(api: LoginApi): LoginService =
// LoginService(api)
//#Provides
//fun provideLoginInteractor(apiService: LoginService): LoginInteractor =
// LoginInteractor(apiService)
}
and
class LoginService #Inject constructor(
val loginAPI: LoginApi,
val loginWithOrgApi: LoginWithOrgApi
) {
fun loginAPIService(user: String, password: String?, extension: String, otp: String?,
hardwareId: String): Single<LoginAPIResponseData> {
password?.let { password ->
return loginAPI.getLogin(user, password, extension, null, hardwareId)
}?: run {
return loginAPI.getLogin(user, null, extension, otp, hardwareId)
}
}
fun loginWithOrg(user: String, password: String?, extension: String, otp: String?,
userId: String, hardwareId: String): Single<LoginAPIResponseData>{
password?.let { password ->
return loginWithOrgApi.getLogin(user, password, extension, null, userId, hardwareId)
}?: run {
return loginWithOrgApi.getLogin(user, null, extension, otp, userId, hardwareId)
}
}
}
and
class LoginInteractor #Inject constructor(
val apiService: LoginService
) {
...
}
You own those classes, so there is no reason to use #field:Inject lateinit var + void inject(MyClass myClass); here.

Related

Testing of secured controller

I have the following controller for which I'm trying to test
#Secured(SecurityRule.IS_AUTHENTICATED)
#Controller
class UserController(private val userService: UserService) {
#Get("/principal")
fun getPrincipal(principal: Principal): Principal = principal
}
My test looks as the following
class Credentials(val username: String, val password: String)
class LoginResponse(
#JsonProperty("access_token") val accessToken: String,
#JsonProperty("expires_in") val expiresIn: Int,
#JsonProperty("refresh_token") val refreshToken: String,
#JsonProperty("token_type") val tokenType: String,
#JsonProperty("username") val username: String)
#Client("/")
interface Client {
#Post("/login")
fun login(#Body credentials: Credentials): LoginResponse
#Get("/principal")
fun getPrincipal(#Header authorizationHeader: String): Principal
}
#MicronautTest
internal class UserControllerTest {
#Inject
lateinit var authenticationConfiguration: AuthenticationConfiguration
#Inject
lateinit var client: Client
#Test
fun getPrincipal() {
val credentials = Credentials(authenticationConfiguration.testUserEmail, authenticationConfiguration.testUserPassword)
val loginResponse = client.login(credentials)
val authorizationHeader = "Authorization:Bearer ${loginResponse.accessToken}"
val principal = client.getPrincipal(authorizationHeader)
}
}
The login works just fine. I get a bearer token and the authorizationHeader looks just fine. But the call to client.getPrincipal(authorizationHeader) fails with io.micronaut.http.client.exceptions.HttpClientResponseException: Unauthorized
Any clue what goes wrong?
It turns out that I can either declare my client as the following. By naming the argument to match the actual http header.
#Client("/")
interface Client {
...
#Get("/principal")
fun getPrincipal(#Header authorization: String): Principal
}
But it's also possible letting the #Header annotation take an argument specifying which http header to target
#Client("/")
interface Client {
...
#Get("/principal")
fun getPrincipal(#Header("Authorization") authorizationValue: String): Principal
}

Unexpected JDWP Error: 103. Exception during Retrofit(2.3.0) GET call

I am getting Unexpected JDWP Error: 103 during call to vk.api to fetch some data.
I have found this topic with related problem, but suggestion from there is already applyed in my application.
So maybe my retrofit configuration is wrong?
Here some code:
Module for DI, using dagger
#Module
class NetworkModule {
#Provides
internal fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(ApiConstants.VK_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
#Provides
internal fun provideGroupApi(retrofit: Retrofit) : GroupApi {
return retrofit.create(GroupApi::class.java)
}
}
Api interface:
interface GroupApi {
#GET(ApiMethods.SEARCH_GROUPS)
fun getGroups(#QueryMap map: Map<String, String?>) : Observable<GroupResponse>
}
object ApiMethods {
const val SEARCH_GROUPS = "groups.search"
}
Inside query:
Model classes:
data class Response<T>(
val count: Int,
val items: List<T>
)
data class GroupResponse(
#SerializedName("response")
#Expose
val response: Response<Group>
)
data class Group(
#SerializedName("id")
#Expose
val id: Int,
#SerializedName("name")
#Expose
val name: String,
#SerializedName("screenName")
#Expose
val screen_name: String,
#SerializedName("isClosed")
#Expose
val is_closed: Int,
#SerializedName("type")
#Expose
val type: String,
#SerializedName("isAdmin")
#Expose
val is_admin: Int,
#SerializedName("isMebmer")
#Expose
val is_member: Int,
#SerializedName("photo_50")
#Expose
val photo_50: String,
#SerializedName("photo_100")
#Expose
val photo_100: String,
#SerializedName("photo_200")
#Expose
val photo_200: String
)
Here is response example from vk.api (I am providing this, because I have a thought that my model is configured not properly):
{
"response": {
"count": 193738,
"items": [{
"id": 26667550,
"name": "ARTY",
"screen_name": "arty_music",
"is_closed": 0,
"type": "page",
"is_admin": 0,
"is_member": 0,
"photo_50": "https://pp.vk.me/...841/1B4wTxXinAc.jpg",
"photo_100": "https://pp.vk.me/...840/Xc_3PikLQ_M.jpg",
"photo_200": "https://pp.vk.me/...83e/kGwRLtSLJOU.jpg"
}, {
"id": 25597207,
"name": "Alexander Popov",
"screen_name": "popov.music",
"is_closed": 0,
"type": "page",
"is_admin": 0,
"is_member": 0,
"photo_50": "https://pp.vk.me/...e8f/g2Z9jU6qXVk.jpg",
"photo_100": "https://pp.vk.me/...e8e/DtYBYKLU810.jpg",
"photo_200": "https://pp.vk.me/...e8d/QRVqdhTvQ4w.jpg"
}, {
"id": 42440233,
"name": "Музыка",
"screen_name": "exp.music",
"is_closed": 0,
"type": "page",
"is_admin": 0,
"is_member": 0,
"photo_50": "https://pp.vk.me/...2d1/52gY6m5ZObg.jpg",
"photo_100": "https://pp.vk.me/...2d0/Jx9DWph_3ag.jpg",
"photo_200": "https://pp.vk.me/...2ce/qsFhk6yEtDc.jpg"
}]
}
}
Could anybody please provide any suggestion ?
UPDATE:
I am also have tried another response model as:
data class Root<T> (
#SerializedName("response")
#Expose
val response: T
)
interface GroupApi {
#GET(ApiMethods.SEARCH_GROUPS)
fun getGroups(#QueryMap map: Map<String, String?>) : Observable<Root<Response<Group>>>
}
but still no luck...
additional code:
Presenter where I call the interactor -> and inside interactor I call GroupApi:
class SearchResultPresenter<V : SearchResultMVPView, I : SearchResultMVPInteractor> #Inject constructor(interactor: I, schedulerProvider: SchedulerProvider, compositeDisposable: CompositeDisposable)
: BasePresenter<V, I>(interactor = interactor, schedulerProvider = schedulerProvider, compositeDisposable = compositeDisposable), SearchResultMVPPresenter<V, I> {
override fun searchGroups(q: String) {
getView()?.showProgress()
interactor?.let {
compositeDisposable.add(it.getGroupList(q)
.compose(schedulerProvider.ioToMainObservableScheduler())
.subscribe { groupResponse ->
getView()?.let {
it.showSearchResult(groupResponse.response.items)
it.hideProgress()
}
})
}
}
}
class SearchResultInteractor #Inject constructor() : SearchResultMVPInteractor {
#Inject
lateinit var groupApi: GroupApi
override fun getGroupList(q: String): Observable<Root<Response<Group>>> = groupApi.getGroups(GroupRequest(q).toMap())
}
I have decided to provide the whole code, where I am applying DI:
#Singleton
#Component(modules = [(AndroidInjectionModule::class), (AppModule::class), (ActivityBuilder::class)])
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(app: MyApplication)
}
Module for fragment:
#Module
class SearchResultFragmentModule {
#Provides
internal fun provideSearchResultInteractor(interactor: SearchResultInteractor): SearchResultMVPInteractor = interactor
#Provides
internal fun provideSearchResultFragment(presenter: SearchResultPresenter<SearchResultMVPView, SearchResultMVPInteractor>)
: SearchResultMVPPresenter<SearchResultMVPView, SearchResultMVPInteractor> = presenter
#Provides
internal fun provideSearchResultProvider(): SearchResultAdapter = SearchResultAdapter(ArrayList())
#Provides
internal fun provideLayoutManager(fragment: SearchResultFragment) : LinearLayoutManager = LinearLayoutManager(fragment.activity)
}
Provider:
#Module
abstract class SearchResultFragmentProvider {
#ContributesAndroidInjector(modules = [(SearchResultFragmentModule::class), (NetworkModule::class)])
internal abstract fun proviceSearchResultFragmentModule(): SearchResultFragment
}
Activity that contains injector for fragments inside of it:
class MainActivity : BaseActivity(), MainMVPView, HasSupportFragmentInjector {
#Inject
internal lateinit var presenter: MainMVPPresenter<MainMVPView, MainMVPInteractor>
#Inject
internal lateinit var fragmentDispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
...
//some code
override fun supportFragmentInjector(): AndroidInjector<Fragment> = fragmentDispatchingAndroidInjector
}
And activity builder:
#Module
abstract class ActivityBuilder {
#ContributesAndroidInjector(modules = [(MainActivityModule::class), (SearchResultFragmentProvider::class)])
abstract fun bindMainActibity(): MainActivity
}
AppComponent:
#Singleton
#Component(modules = [(AndroidInjectionModule::class), (AppModule::class), (ActivityBuilder::class)])
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(app: MyApplication)
}
Have you tried to inject retrofit (instead of GroupApi) and then call
retrofit.create(GroupApi::class.java).getGroups(GroupRequest(q).toMap())
in your SearchResultInteractor? Also you can annotate fun provideRetrofit() as Singleton.
If some one still watching this post - I am sorry, I've made a mistake.
Retrofit is working properly, the issue was in uninitialized view at presenter class, so when I was calling api method groupApi.getGroups(GroupRequest(q).toMap()) in debug - exception was appearing. But problem was in view class.

moshi custom qualifier annotation to serialise null on one property only

I'd like to serialise null for only one property in my JSON body that is going on a PUT. I don't want to serialize null for any other types in the object. Model class is like this
#Parcel
class User #ParcelConstructor constructor(var college: College?,
var firstname: String?,
var lastname: String?,
var email: String?,
var active: Boolean = true,
var updatedAt: String?,
var gender: String?,
var picture: String?,
var id: String?,
#field: [CollegeField] var collegeInput: String?,
#field: [CollegeField] var otherCollege: String?,)
I only want to serialise collegeInput and otherCollege fields if either of them are null. For example
val user = User(firstname = "foo", lastname=null, collegeInput="abcd", otherCollege = null)
Json will look something like this:
{"user":{
"firstname": "foo",
"collegeInput": "abcd",
"otherCollege": null
}}
Where otherCollege is null, lastname is omitted from the object as by default moshi does not serialise nulls which is what I want, but qualifer fields should be serialized with null values
I tried using
class UserAdapter {
#FromJson
#CollegeField
#Throws(Exception::class)
fun fromJson(reader: JsonReader): String? {
return when (reader.peek()) {
JsonReader.Token.NULL ->
reader.nextNull()
JsonReader.Token.STRING -> reader.nextString()
else -> {
reader.skipValue() // or throw
null
}
}
}
#ToJson
#Throws(IOException::class)
fun toJson(#CollegeField b: String?): String? {
return b
}
#Retention(AnnotationRetention.RUNTIME)
#JsonQualifier
annotation class CollegeField
I added the adapter to moshi but it never gets called
#Provides
#Singleton
fun provideMoshi(): Moshi {
return Moshi.Builder()
.add(UserAdapter())
.build()
}
#Provides
#Singleton
fun provideRetrofit(client: OkHttpClient, moshi: Moshi, apiConfig: ApiConfig): Retrofit {
return Retrofit.Builder()
.baseUrl(apiConfig.baseUrl)
.client(client)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
}
Your toJson adapter method will return null when the qualified string value is null, and the JsonWriter will not write the null value.
Here is a qualifier and adapter factory to install that will work.
#Retention(RUNTIME)
#JsonQualifier
public #interface SerializeNulls {
JsonAdapter.Factory JSON_ADAPTER_FACTORY = new JsonAdapter.Factory() {
#Nullable #Override
public JsonAdapter<?> create(Type type, Set<? extends Annotation> annotations, Moshi moshi) {
Set<? extends Annotation> nextAnnotations =
Types.nextAnnotations(annotations, SerializeNulls.class);
if (nextAnnotations == null) {
return null;
}
return moshi.nextAdapter(this, type, nextAnnotations).serializeNulls();
}
};
}
Now, the following will pass.
class User(
var firstname: String?,
var lastname: String?,
#SerializeNulls var collegeInput: String?,
#SerializeNulls var otherCollege: String?
)
#Test fun serializeNullsQualifier() {
val moshi = Moshi.Builder()
.add(SerializeNulls.JSON_ADAPTER_FACTORY)
.add(KotlinJsonAdapterFactory())
.build()
val userAdapter = moshi.adapter(User::class.java)
val user = User(
firstname = "foo",
lastname = null,
collegeInput = "abcd",
otherCollege = null
)
assertThat(
userAdapter.toJson(user)
).isEqualTo(
"""{"firstname":"foo","collegeInput":"abcd","otherCollege":null}"""
)
}
Note that you should use the Kotlin support in Moshi to avoid the #field: oddities.
Try approach from my gist:
https://gist.github.com/OleksandrKucherenko/ffb2126d37778b88fca3774f1666ce66
In my case I convert NULL from JSON into default double/integer value. You can easily modify the approach and make it work for your specific case.
p.s. its JAVA, convert it to Kotlin first.

Mockito retrofit2 with MVP architecture

I think somethings wrong about my code in TeamImplsTest, and i need advice :D
This is my code
API interface
interface API {
#GET("lookupteam.php")
fun getTeam(#Query("id") id: String): Call<TeamModel>
}
TeamPresenter
interface MatchPresenter {
fun loadTeamDetail(team_id: String)
}
TeamImpls
class TeamImpls(val teamView: TeamView) : TeamPresenter {
override fun loadTeamDetail(team_id: String) {
val call = RetrofitConfig().getApi().getTeam(team_id)
call.enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful()) {
val res = response.body()
res?.let { teamView.onSuccess(it) }
}
}
override fun onFailure(call: Call, t: Throwable) {
Log.e("PrevMatchFragment", t.toString())
}
})
}
}
TeamModel
data class TeamModel(
val teams: ArrayList
)
data class TeamModeLResult(
val idTeam: String,
val strTeam: String,
val strAlternate: String,
val strSport: String,
val strStadium: String,
val strTeamBadge: String
)
And
This my TeamImplsTest
class TeamImplsTest {
#Mock
private lateinit var teamView: TeamView
#Mock
private lateinit var teamPresenter: TeamPresenter
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
teamPresenter = TeamImpls(teamView)
}
#Test
fun loadTeamDetail() {
val teams = TeamModel(arrayListOf())
val teamId = "133613"
teamPresenter.loadTeamDetail(teamId)
Mockito.verify(teamView).onSuccess(teams)
}
}
i got error
Wanted but not invoked:
teamView.onSuccess(TeamModel(teams=[]));
-> at com.fathurradhy.matchschedule.domain.presenter.TeamImplsTest.loadTeamDetail(TeamImplsTest.kt:34)
Actually, there were zero interactions with this mock.
Wanted but not invoked:
teamView.onSuccess(TeamModel(teams=[]));
-> at com.fathurradhy.matchschedule.domain.presenter.TeamImplsTest.loadTeamDetail(TeamImplsTest.kt:34)
Actually, there were zero interactions with this mock.
You're not mocking the API call as loadTeamDetail creates its own API instance.
To enable you to test the API call behaviour you could provide the API instance through your constructor, e.g.
class TeamImpls(private val api: API, private val teamView: TeamView) : TeamPresenter {
override fun loadTeamDetail(team_id: String) {
val call = api.getTeam(team_id)
This would then allow you to mock the api behaviour and verify the presenter calls the correct method when the call fails/succeeds, e.g.
class TeamImplsTest {
#Mock
private lateinit var teamView: TeamView
#Mock
private lateinit var api: API
#Mock
private lateinit var teamPresenter: TeamPresenter
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
teamPresenter = TeamImpls(api, teamView)
}
#Test
fun loadTeamDetail() {
val teams = TeamModel(arrayListOf())
val teamId = "133613"
// Use retrofit-mock to create your mockResponse.
// See: https://github.com/square/retrofit/tree/master/retrofit-mock
Mockito.`when`(api.getTeam(teamId)).thenReturn(Calls.response(teams)
teamPresenter.loadTeamDetail(teamId)
Mockito.verify(teamView).onSuccess(teams)
}
}

Android Room + Kotlin pattern

the Android Room documentation says that we should follow the singleton design pattern when instantiating an AppDatabase object.
I was thinking about it, and I would like to know if its recommended to put the AppDatabase class inside my Application class. Or if I can use the Kotlin singleton for that.
Let's say I have a DAO called CarroDAO and class CarrosDatabase that is a RoomDatabase.
Is it ok to create a DatabaseManager class using a Kotlin object/singleton ?
object DatabaseManager {
private var dbInstance: CarrosDatabase
init {
val appContext = MyApplication.getInstance().applicationContext
dbInstance = Room.databaseBuilder(
appContext,
CarrosDatabase::class.java,
"mybd.sqlite")
.build()
}
fun getCarroDAO(): CarroDAO {
return dbInstance.carroDAO()
}
}
So I can get the DAO class like this:
val dao = DatabaseManager.getCarroDAO()
According to Android documentation, we can create a database instance using the singleton design pattern as follows
Create a room database entity
#Entity
data class User(
#PrimaryKey var uid: Int,
#ColumnInfo(name = "first_name") var firstName: String?,
#ColumnInfo(name = "last_name") var lastName: String?
)
Create DAO class
#Dao
interface UserDao {
#Query("SELECT * FROM user")
fun getAll(): List<User>
#Query("SELECT * FROM user WHERE uid IN (:userIds)")
fun loadAllByIds(userIds: IntArray): List<User>
#Query("SELECT * FROM user WHERE first_name LIKE: first AND " +
"last_name LIKE :last LIMIT 1")
fun findByName(first: String, last: String): User
#Insert
fun insertAll(vararg users: User)
#Delete
fun delete(user: User)
}
Create database with singleton pattern
#Database(entities = arrayOf(User::class), version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
#Volatile
private var instance: AppDatabase? = null
fun getInstance(
context: Context
): AppDatabase = instance ?: synchronized(this) {
instance ?: buildDatabase(context).also { instance = it }
}
private fun buildDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(
context,
AppDatabase::class.java,
"database-name"
).build()
}
}
}
You can get database instance by following code
var databaseInstance=AppDatabase.getInstance(context)