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

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.

Related

Accessing the Room database from ViewModel

I'm about to refactor my app to use a ViewModel. This is the database:
#Database(entities = [TimeStamp::class], version = 1, exportSchema = false)
abstract class RoomDB : RoomDatabase() {
abstract fun timeStampDao(): TimeStampDao
companion object {
#Volatile
private lateinit var db: RoomDB
fun getInstance(context: Context): RoomDB {
synchronized(this) {
if (!::db.isInitialized) {
db = Room.databaseBuilder(context, RoomDB::class.java, "db").build()
}
return db
}
}
}
}
And this is my ViewModel:
class MainViewModel : ViewModel() {
val timeStamps: MutableLiveData<List<TimeStamp>> by lazy {
MutableLiveData<List<TimeStamp>>().also {
viewModelScope.launch {
val timeStamps = RoomDB.getInstance(_NO_CONTEXT_).timeStampDao().getAll()
}
}
}
}
Unfortunately, I don't have the context available in the ViewModel. Several answers to this question say that I should not try access the context in a ViewModel.
Do I need to refactor my RoomDB as well? Is there a generally accepted pattern how to do this?

Room cannot verify the data integrity. Looks like you've changed schema.... What's wrong?

I am using Room in my app with two entities. The whole implementation is below.
The Problem is, the given scheme is fixed, which means I do not change anything regarding DB. When I provide a new version of my app to Users over Google Play Console, I get the following issue in Cryshlytics although I did not change anything for DB, just edited UI or another things, which definetly nothing have to do with DB:
Fatal Exception: java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.
at androidx.room.RoomOpenHelper.checkIdentity(RoomOpenHelper.java:154)
at androidx.room.RoomOpenHelper.onOpen(RoomOpenHelper.java:135)
.......
Now I am not sure if I change the version of DB, it would work. What is wrong here?
BTW the DB is called from a Fragment like this
val mainDb: MainRepository by lazy { MainRepository(requireContext()) }
val stateDb: StateRepository by lazy { StateRepository(requireContext()) }
What's wrong here?
AppDatabase:
#Database(entities = [Main::class, State::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract val mainDao: MainDao
abstract val stateDao: StateDao
companion object {
private var INSTANCE: AppDatabase? = null
fun getInstance(context: Context): AppDatabase? =
INSTANCE ?: synchronized(AppDatabase::class) {
INSTANCE = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
MY_DB
).allowMainThreadQueries()
.build()
return INSTANCE
}
}
}
Dao:
#Dao
interface StateDao {
#Query("SELECT * FROM $STATE")
fun getAll(): List<State>
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(state: State)
#Update
fun update(state: State)
#Query("DELETE FROM $STATE")
fun drop()
}
#Dao
interface MainDao {
#Query("SELECT * FROM $MAIN")
fun getAll(): List<Main>
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(main: Main)
#Update
fun update(main: Main)
#Query("DELETE FROM $MAIN")
fun drop()
}
Main:
#Entity(tableName = MAIN)
data class Main(
#PrimaryKey #ColumnInfo(name = NUMBER) val number: Int,
#ColumnInfo(name = CARD) val car: String? = EMPTY,
#ColumnInfo(name = MODEL) val model: String? = EMPTY
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readString(),
parcel.readString()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(number)
parcel.writeString(car)
parcel.writeString(model)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Main> {
override fun createFromParcel(parcel: Parcel): Main {
return Main(parcel)
}
override fun newArray(size: Int): Array<Main?> {
return arrayOfNulls(size)
}
}
}
State:
#Entity(tableName = STATE)
data class State(
#PrimaryKey #ColumnInfo(name = NUMBER) val number: Int,
#ColumnInfo(name = STATE) val state: String? = EMPTY
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readString()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(number)
parcel.writeString(question)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<State> {
override fun createFromParcel(parcel: Parcel): State {
return State(parcel)
}
override fun newArray(size: Int): Array<State?> {
return arrayOfNulls(size)
}
}
}
Repository:
class MainRepository(context: Context) {
private val mainDao = AppDatabase.getInstance(context)?.mainDao
fun getAll(): List<Main>? {
return mainDao?.getAll()
}
fun insert(main: Main) {
AsyncInsert(mainDao).execute(main)
}
fun update(main: Main) {
mainDao?.update(main)
}
fun drop() {
mainDao?.drop()
}
private class AsyncInsert(private val dao: MainDao?) : AsyncTask<Main, Void, Void>() {
override fun doInBackground(vararg p0: Main?): Void? {
p0[0]?.let { dao?.insert(it) }
return null
}
}
}
class StateRepository(context: Context) {
private val stateDao = AppDatabase.getInstance(context)?.stateDao
fun drop() {
stateDao?.drop()
}
fun getAll(): List<State>? {
return stateDao?.getAll()
}
fun insert(state: State) {
AsyncInsert(stateDao).execute(state)
}
fun update(state: State) {
stateDao?.update(state)
}
private class AsyncInsert(private val dao: StateDao?) : AsyncTask<State, Void, Void>() {
override fun doInBackground(vararg p0: State?): Void? {
p0[0]?.let { dao?.insert(it) }
return null
}
}
}
Now I am not sure if I change the version of DB, it would work. What is wrong here?
Changing the version would probably not work as the schema, as far as Room is concerned, has changed.
There is either a bug or the schema has been changed.
However, changing the version, would, with a Migration that does nothing (so as to not get a "no migration specified" error), then fail but importantly with an expected (what Room expects the schema to be according to the Entities) found (the schema that exists) discrepancy. This, if there is no bug, could then be used to ascertain what has been changed.

Parsing api using Retrofit and GSON

I'm parsing API and it's logging in the logcat, but I have a problem while retrieving it and using that data in a recycleview. These are my code snippets:
class MainActivity : AppCompatActivity() {
private val users = arrayListOf<User>()
private lateinit var adapter: RecyclerViewAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
init()
}
private fun init() {
adapter = RecyclerViewAdapter(users)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = adapter
val myViewModel : UsersViewModel by viewModel()
myViewModel.getAllUsers().observe(this, Observer {
users.add(it)
adapter.notifyDataSetChanged()
})
myViewModel.getUsers()
d("allUsers", users.size.toString())
}
}
I cannot set the data in a recyclerview, can anyone give me a hint? I could not find a proper source or code snippet to understand how I'm able to parse the data using a converter.
class UsersRequest {
private var retrofit = Retrofit.Builder()
.baseUrl("https://reqres.in/api/")
.addConverterFactory(ScalarsConverterFactory.create())
.build()
private var service = retrofit.create(ApiService::class.java)
interface ApiService {
#GET("users?page=1")
fun getRequest(): Call<String>
}
fun getRequest(callback: CustomCallback) {
val call = service.getRequest()
call.enqueue(onCallback(callback))
}
private fun onCallback(callback: CustomCallback): Callback<String> = object : Callback<String> {
override fun onFailure(call: Call<String>, t: Throwable) {
d("response", "${t.message}")
callback.onFailure(t.message.toString())
}
override fun onResponse(call: Call<String>, response: Response<String>) {
d("response", "${response.body()}")
callback.onSuccess(response.body().toString())
}
}
}
class RecyclerViewAdapter(private val users: ArrayList<User>) :
RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.user_layout,
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
return holder.onBind()
}
override fun getItemCount() = users.size
private lateinit var user:User
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun onBind() {
user = users[adapterPosition]
itemView.name.text = user.first_name
}
}
}
User(
val id: Int,
val email: String,
val first_name: String,
val last_name: String,
val avatar: String
)
with adapter class RecyclerViewAdapter(private val users: MutableList<User>) : RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>() { ...
Add this method to your adapter :
fun setUsers(usersList: List<User>) {
this.users.clear()
this.users.addAll(usersList)
notifyDataSetChanged()
}
and in MainActivity simply put :
myViewModel.getAllUsers().observe(this, Observer {
users -> hideLoading() // If you have a progress bar, here you can hide it
users?.let {
adapter.setUsers(users) }
})

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)
}
}

Kotlin, reduce duplicated code

Every my API service interface class have create static method,
interface AuthApiService {
#FormUrlEncoded
#POST("api/auth/login")
fun postLogin(#Field("username") username: String, #Field("password") password: String):
io.reactivex.Observable<LoginApiResponse>
companion object Factory {
fun create(): AuthApiService {
val gson = GsonBuilder().setLenient().create()
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl("http:192.168.24.188:8080")
.build()
return retrofit.create(AuthApiService::class.java)
}
}
}
interface BBBApiService {
companion object Factory {
fun create(): BBBApiService {
val gson = GsonBuilder().setLenient().create()
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl("http:192.168.24.188:8080")
.build()
return retrofit.create(BBBApiService::class.java)
}
}
}
But, I want to define the create() method only once.
So I made the ApiFactory class,
interface ApiFactory {
companion object {
inline fun <reified T>createRetrofit(): T {
val gson = GsonBuilder().setLenient().create()
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl("http://192.168.24.188:8080")
.build()
return retrofit.create(T::class.java)
}
}
}
interface AuthApiService {
#FormUrlEncoded
#POST("api/auth/login")
fun postLogin(#Field("username") username: String, #Field("password") password: String):
io.reactivex.Observable<LoginApiResponse>
companion object Factory {
fun create(): AuthApiService {
return ApiFactory.createRetrofit()
}
}
But, still, I need to define the create() method in AuthApiService.
Is there any a way implement the ApiFactory class to SubApi classes so that I don't have to define the create method in each child classes?
A simple solution is just to call the function of your ApiFactory directly:
val authApiService = ApiFactory.createRetrofit<AuthApiService>()
But if you want to be able to call AuthApiService.create(), then you can define a marker interface, say, ApiFactoryClient<T>, and mark an empty companion object with it.
interface ApiFactoryClient<T>
interface AuthApiService {
/* ... */
companion object : ApiFactoryClient<AuthApiService>
}
And then make an extension function that works with ApiFactoryClient<T>:
inline fun <reified T> ApiFactoryClient<T>.create(): T = ApiFactory.createRetrofit<T>()
And the usage would be:
val authApiService = AuthApiService.create()
You can modify your ApiFactory like this:
interface ApiFactory {
companion object {
inline fun <reified T>createRetrofit(klass: KClass<T>): T {
val gson = GsonBuilder().setLenient().create()
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl("http://192.168.24.188:8080")
.build()
return retrofit.create(klass.java)
}
}
}
And then use it to create different service instances:
val authApiService = ApiFactory.createRetrofit(AuthApiService::class)