I newbie on kotlin, its my firts app test. I trying to do a retrofit call (POST) but i get sintax error on create code.
My syntax error: no type arguments expected for CallBack
this is my doLogin function on presenter layer (i got error here):
override fun doLogin() {
val call = RetrofitInitializer().loginApiContract().login()
call.enqueue(object: Callback<UserAccount> {
override fun onResponse(call: Call<UserAccount>?,
response: Response<UserAccount) {
}
override fun onFailure(call: Call<UserAccount>?,
t: Throwable?) {
}
})
}
and that is my retrofit initializer:
class RetrofitInitializer {
private val retrofit = Retrofit.Builder()
.baseUrl("http://192.168.0.23:8080/")
.addConverterFactory(GsonConverterFactory.create())
.build()
fun loginApiContract() : LoginApiContract{
return retrofit.create(LoginApiContract::class.java)
}
}
that is my interface of call:
interface LoginApiContract {
#POST("login")
fun login() : Call<UserAccount>
#GET("statements")
fun getStatements()
}
Best practice with kotlin - use rxjava calladapter factory.
Try to add dependency
compile "com.squareup.retrofit2:adapter-rxjava2:"
and add call adapter factory
private val retrofit = Retrofit.Builder()
.baseUrl("http://192.168.0.23:8080/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build()
Related
I'm new to the Koin so hopefully someone will be able to point out the direction of the issue I'm encountering.
I've an Interface class:
interface UserApi {
#POST("/refreshToken")
#Headers("Accept: application/json")
suspend fun refreshToken(#Body x: X): TokenResponseDto
}
I've a class where I use UserApi to do API call.
class TokenAuthenticator(
private val userApi: UserApi
) : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? = synchronized(this) {
runBlocking { userApi.refreshToken() }
}
}
This far everything is fine, but now I want to Inject TokenAuthenticator class. If I remove constructor for testing purposes, I can see app running and everything is fine, but when I add userApi constructor variable - as I need it, I get and error.
I've NetworkModule that looks like this:
val networkModule = module {
single<UserApi> {
Retrofit.Builder()
.client(get(named("httpClient")))
.baseUrl(get<String>(named("...")))
.addConverterFactory(
...
)
.build()
.create(UserApi::class.java)
}
single(named("httpClient")) {
val tokenAuthenticator: TokenAuthenticator = get()
OkHttpClient.Builder()
.authenticator(tokenAuthenticator)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
}
single {
TokenAuthenticator(get())
}
}
Error:
at org.koin.core.instance.SingleInstanceFactory$get$1.invoke(SingleInstanceFactory.kt:53)
UPDATE: Someone advised to use a lambda function in TokenAuthenticator. I think this solution is more simplier.
class TokenAuthenticator(
private val userApi : () -> UserApi
) {
// ...
fun authenticate(...) {
userApi().refreshToken()
}
}
In this case you can define your koin definition like this.
single {
TokenAuthenticator {
get()
}
}
My answer was:
There may be better solutions but this is a rushed one. You may improve it.
Let's decouple TokenAuthenticator and UserApi. They will be connected later by a TokenRefresher.
interface TokenRefresher {
fun refreshToken()
}
class TokenAuthenticator(
private val tokenRefresher: TokenRefresher
) : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? = synchronized(this) {
runBlocking { tokenRefresher.refreshToken() }
}
}
Add a token refresher into koin module.
val networkModule = module {
single<TokenRefresher> {
object : TokenRefresher {
// now use the userApi
override fun refreshToken() {
val userApi: UserApi = get()
userApi.refreshToken()
}
}
}
single<UserApi> {
Retrofit.Builder()
.client(get(named("httpClient")))
.baseUrl(get<String>(named("...")))
.addConverterFactory(
...
)
.build()
.create(UserApi::class.java)
}
single(named("httpClient")) {
val tokenAuthenticator: TokenAuthenticator = get()
OkHttpClient.Builder()
.authenticator(tokenAuthenticator)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
}
single {
TokenAuthenticator(get())
}
}
Hope it helps.
I am trying to inject retrofit APIServices dependency into the model class. Here is My API Module Source Code:
#Module
#InstallIn(SingletonComponent::class)
object ApiModule {
#Singleton
#Provides
fun providesHttpLoggingInterceptor() = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
#Singleton
#Provides
fun providesOkHttpClient(httpLoggingInterceptor: HttpLoggingInterceptor): OkHttpClient =
OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.build()
#Singleton
#Provides
fun providesRetrofit(okHttpClient: OkHttpClient): Retrofit =
Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(ApiConfig.BASE_URL)
.client(okHttpClient)
.build()
#Singleton
#Provides
#Named("ApiService")
fun providesApiService(retrofit: Retrofit):ApiServices =
retrofit.create(ApiServices::class.java)
}
For User Registration, I am using MVP Architecture Pattern where FragmentRegistration.kt is view layer, RegistrationModel is model layer class
When I inject ApiServices dependency into FragmentRegistration, it works fine. But when I try to inject it into model layer class, which is RegistrationModel, It doesn't work.
RegistrationModel:
class RegistrationModel(
val presenter: RegistrationContract.Presenter
) : RegistrationContract.Model {
#Inject
#Named("ApiService")
lateinit var apiServices: ApiServices
override fun onDataReady(registrationData: RegistrationData) {
val map = mapOf(
"Accept" to "application/json",
"Content-Type" to "application/json"
)
apiServices.userRegistration(map, registrationData)
.enqueue(object : Callback<RegistrationResponse> {
override fun onResponse(
call: Call<RegistrationResponse>,
response: Response<RegistrationResponse>
) {
if (response.isSuccessful) {
Log.d(TAG, "onDataReady: ${response.body().toString()}")
} else {
val apiFailure = APIFailure(
response.code(),
response.message()
)
presenter.onSignupFailure(apiFailure)
Log.d(TAG, "onDataReady: Error ${response.code()}")
Log.d(TAG, "onDataReady: Error Body ${response.errorBody()}")
}
}
override fun onFailure(call: Call<RegistrationResponse>, t: Throwable) {
presenter.onSignupFailure(
APIFailure(-1, t.toString())
)
Log.d(TAG, "onFailure: $t")
}
})
}
companion object {
const val TAG = "RegistrationModel"
}
}
In the above's Code,
#Inject
#Named("ApiService")
lateinit var apiServices: ApiServices
this dependency injection is not working.
You are trying to inject a filed provided by Hilt into a class which is not managed by Hilt. This will not work out of the box. You have to define EntryPoint for you custom class, so the Hilt can perform injection. You can read how to do that here: https://developer.android.com/training/dependency-injection/hilt-android#not-supported
interface ApiInterface {
#Headers("Content-Type: application/json")
#POST("testgetmemes/")
fun getMemes(): Call<List<Memes>>
}
object ApiClient {
var BASE_URL:String="https://www.blaaa.com/"
val getClient: ApiInterface
get() {
val gson = GsonBuilder()
.setLenient()
.create()
val client = OkHttpClient.Builder().build()
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
return retrofit.create(ApiInterface::class.java)
}
}
and inside a function :
val call: Call<List<Memes>> = ApiClient.getClient.getMemes()
call.enqueue(object : Callback<List<Memes>> {
override fun onResponse(call: Call<List<Memes>>?, response: Response<List<Memes>>) {
Log.d(tagg, response!!.body()!!.toString())
//setMemes(response!!.body()!!, gal)
}
override fun onFailure(call: Call<List<Memes>>?, t: Throwable?) {
Log.d(tagg, t!!.toString())
}
})
I want to get the JSONArray instead of this ready to use Model List! Otherwise I would have to do a lot of changes and workarounds because I usually use beautiful loopj, this retrofit2 thing is only to make APIs work on older Android versions.
I tried that by replacing all <List<Memes>> with <JSONArray> but then I get this response:
com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 2 path $
How to do this??
This is the way I do it now, using Gson:
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
..
JSONArray(Gson().toJson(response.body()))
No need to replace <List<Memes>> with <JSONArray>
Im trying to setup Retrofit2 with suspend functions but I am having unexpected error while in offline mode (all good in online mode).
ApiService:
interface ApiService {
#GET("v2/153")
suspend fun fetchData(): Response<DataModel>
}
Retrofit Client:
fun provideDefaultOkhttpClient(context: Context): OkHttpClient {
val cacheSize = (5 * 1024 * 1024).toLong()
return OkHttpClient.Builder()
.cache(Cache(context.cacheDir, cacheSize))
.build()
}
fun provideRetrofit(client: OkHttpClient): Retrofit {
return Retrofit.Builder()
.client(client)
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.build()
}
fun provideApiService(retrofit: Retrofit): ApiService =
retrofit.create(ApiService::class.java)
DataRepository:
class DataRepository(private val api: ApiService) {
suspend fun getData(): List<DataModel>? {
val myData = api.fetchData()
return if (myData.isSuccessful) {
myData.body()
} else {
null
}
}
}
While debugging the app I see the DataRepository->api.fetchData() being called, and from there it hangs and I get the following error:
2019-06-28 10:35:46.136 10876-10876/com.myapp.name.debug E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.myapp.name.debug, PID: 10876
What am I missing? Or am I just wrongly using suspend function to retrieve data?
Hi i get the following error:
java.lang.IllegalArgumentException: Unable to create #Body converter for class com.jr.app.models.ExampleData (parameter #1)
Here is my ExampleData.kt
data class ExampleData(val id: String,
val firstName: String,
val secondName: String,
val profilImages: String,
val info: String) {
}
My interface retrofit
interface UsersService {
#GET("/usersProfile")
fun getAllUsers(): Call<List<ExampleData>>
#POST("/usersProfile")
fun addUser(#Body exampleData: ExampleData): Call<ResponseBody>
}
function that addsUser
override fun addUser(user: ExampleData) {
val retrofit = Retrofit.Builder().baseUrl(baseUrl).client(httpAuthClient).build();
val userService = retrofit.create(UsersService::class.java);
userService.addUser(user).enqueue(callbackResponse);
}
private val httpAuthClient: OkHttpClient
get() {
val okHttpClient = OkHttpClient().newBuilder().addInterceptor { chain ->
val originalRequest = chain.request()
val builder = originalRequest.newBuilder().header(authorizeHeader,
Credentials.basic(userName, password))
val newRequest = builder.build()
chain.proceed(newRequest)
}.build()
return okHttpClient
}
Add the gradle dependency to your project:
compile 'com.squareup.retrofit2:converter-gson:VERSION_OF_RETROFIT'
When you build an instance of retrofit add this line:
.addConverterFactory(GsonConverterFactory.create())
In your project building the retrofit object will look like:
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.client(httpAuthClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
I believe this has nothing to do with Kotlin but your Retrofit configuration and your data class ExampleData.
Retrofit has no idea how to serialize your instance of ExampleData to JSON. You need to add a specific converter factory when creating instance of Retrofit client (see Builder#addConverterFactory method for details).