Why I get Unable to create converter exception from retrofit? - android

My problem is, when I use Call inside my retrofit api, I get this exception:
Unable to create converter for retrofit2.Call<java.util.List>
for method IntegApi.getAllProductBarcodeAsync
The intersting thing, if I don't use Call then the error message is gone.
I want to use Call, becuse I would like to use a custom response class, because i want the know when the api sends 404 status, and then I want to skip this exception.
I use Moshi to convert Json
Sync function:
private suspend fun syncProductBarcodes() {
try {
val productBarcodes = api.getAllProductBarcodeAsync().await()
if(productBarcodes.isSuccessful) {
productBarcodeRepository.deleteAndInsertAll(productBarcodes.body() ?: emptyList())
addOneToStep()
}
}catch (e: Exception){
Timber.d(e)
throw e
}
}
Api:
#GET("Product/GetAllBarcode")
suspend fun getAllProductBarcodeAsync(): Call<List<ProductBarcode>>
Entity class:
#Entity(
tableName = ProductBarcode.TABLE_NAME
)
#JsonClass(generateAdapter = true)
class ProductBarcode(
#PrimaryKey
#ColumnInfo(name = "id")
#Json(name = "Id")
val id: String = "",
#ColumnInfo(name = "product_id", index = true)
#Json(name = "ProductId")
var ProductId: String = "",
#ColumnInfo(name = "barcode", index = true)
#Json(name = "Barcode")
var barcode: String = ""
) {
companion object {
const val TABLE_NAME = "product_barcode"
}
}
ExtensionFun:
suspend fun <T> Call<T>.await(): Response<T> = suspendCoroutine { continuation ->
val callback = object : Callback<T> {
override fun onFailure(call: Call<T>, t: Throwable) {
continuation.resumeWithException(t)
}
override fun onResponse(call: Call<T>, response: Response<T>) =
continuation.resumeNormallyOrWithException {
if (response.isSuccessful || response.code() == 404) {
return#resumeNormallyOrWithException response
} else {
throw IllegalStateException("Http error ${response.code()}, request:${request().url()}")
}
}
}
enqueue(callback)
}
ApiModule:
#Provides
#Singleton
fun provideMoshi(): Moshi {
return Moshi.Builder()
.add(DateConverter())
.add(BigDecimalConverer())
.add(KotlinJsonAdapterFactory())
.build()
}
fun provideIntegApi(
#Named("base_url") url: String, moshi: Moshi,
prefManager: PrefManager
): IntegApi {
var builder = OkHttpClient.Builder()
builder = BuildTypeInitializations.setupInterceptor(builder)
builder.addInterceptor { chain ->
val request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer ${prefManager.token}")
.addHeader("Connection", "close")
.build()
val response = chain.proceed(request)
return#addInterceptor response
}
.readTimeout(5, TimeUnit.MINUTES)
.writeTimeout(5, TimeUnit.MINUTES)
.connectTimeout(5, TimeUnit.MINUTES)
.retryOnConnectionFailure(true)
return Retrofit.Builder()
.baseUrl(url)
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.client(builder.build())
.build()
.create(IntegApi::class.java)
}
I don't know exactly why, but If the Api send 404 response, after than I get the Unable to crate converter exception, but I the server send the response, the error message is gone.
Update:
If is use this :
#get:GET("Product/GetAllBarcode")
val getAllProductBarcodeAsync: Call<List<ProductBarcode>>
instead of this:
#GET("Product/GetAllBarcode")
suspend fun getAllProductBarcodeAsync(): Call<List<ProductBarcode>>
There won't be error, and everything works fine, but I don't understand what's the problem
Update2
I changed Moshi to Jackson, and it doesn't throw converter error like moshi, but throw Http 404 Error which is more friendlier for me, but I' m not completely satisfied. I created await() fun because of Http 404 errors, and I think this bunch of code skipped because of http 404?

Finally I found the issue, I made a mistake, because I tried to use suspend fun and retrofit Call at the same time. I deleted the suspend keyword, and it works perfectly.

Related

Cannot make Post request in Retrofit Android (Kotlin)

I've been developing an Android Q&A app using Jetpack Compose. I've been trying to make Post requests in Retrofit but the data I send isn't on my API website. I've succeeded in making Get requests though. I've read many documents but I cannot find out what is wrong with this code.
This is data class.
data class UsersEntity(
val id: Int? = null,
val name: String? = null,
val uid: String? = null
)
This is Service interface.
interface UserService {
#POST("createusers")
fun createUsers(#Body usersinfo: UsersEntity): Call<Unit>
}
When I click a button, I'd like to send data to the server. I get the log "Hi, good job" but I cannot see the data on my API.
Button(
onClick = {
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.*****.com/")
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
val service: UserService = retrofit.create(UserService::class.java)
val usersInfo = UsersEntity(
3, "Alex", "164E92FC-D37A")
service.createUsers(usersInfo).enqueue(object: Callback<Unit> {
override fun onResponse(call: Call<Unit>, response: Response<Unit>) {
Log.d("Hi", "good job")
}
override fun onFailure(call: Call<Unit>, t: Throwable) {
Log.d("Hi", "error")
}
})
}
I changed the code like this.
Button(
onClick = {
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.*****.com/")
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
thread {
try {
val service: UserService = retrofit.create(UserService::class.java)
val usersInfo = UsersEntity(
3, "Alex", "164E92FC-D37A")
service.createUsers(usersInfo).enqueue(object: Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
Log.d("Response", "${response.body()}")
}
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
Log.d("Hi", "error")
}
})
} catch (e: Exception) {
Log.d("response", "debug $e")
}
}
},
Could someone help me? Thank you.
I think your baseurl shouldn't end with a slash. Try this.
.baseUrl("https://api.*****.com")
And for your interface (also the Call<ResponseBody>):
interface UserService {
#POST("/createusers/")
fun createUsers(#Body usersinfo: UsersEntity): Call<ResponseBody>
}
Got some issues with this in the past so this might help. If not it atleasts cleans the code a bit :p
Also you can use ProxyMan to intercept your request and read what your application is actually sending to the server, might be a issue to find there!
Proxyman.io

Retrofit POST response conversion fails without a trace

I'm building an app for a company using MVVM & clean architecture so I've created 3 modules, the app module (presentation layer), the data module (data layer) & the domain module (domain/interactors layer). Now, in my data module, I'm using Retrofit and Gson to automatically convert the JSON I'm receiving from a login POST request to my kotlin data class named NetUserSession that you see below. The problem I'm having is that the logging interceptor prints the response with the data in it normally but the response.body() returns an empty NetUserSession object with null values which makes me think that the automatic conversion isn't happening for some reason. Can somebody please tell me what I'm doing wrong here?
KoinModules:
val domainModule = module {
single<LoginRepository> {LoginRepositoryImpl(get())}
single { LoginUseCase(get()) }
}
val presentationModule = module {
viewModel { LoginViewModel(get(),get()) }
}
val dataModule = module {
single { ApiServiceImpl().getApiService() }
single { LoginRepositoryImpl(get()) }
}
}
Api interface & retrofit:
interface ApiService {
#POST("Login")
fun getLoginResult(#Body netUser: NetUser) : Call<NetUserSession>
#GET("Books")
fun getBooks(#Header("Authorization") token:String) : Call<List<NetBook>>
}
class ApiServiceImpl {
fun getApiService(): ApiService {
val logging = HttpLoggingInterceptor()
logging.setLevel(HttpLoggingInterceptor.Level.BODY)
//TODO:SP Remove the interceptor code when done debugging
val client: OkHttpClient = OkHttpClient.Builder()
.addInterceptor(logging)
.build()
val retrofit = Retrofit.Builder().baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
// tell retrofit to implement the interface of our api
return retrofit.create(ApiService::class.java)
}
}
NetUserSession:
data class NetUserSession(
#SerializedName("expires_in")
val expires_in: Int,
#SerializedName("token_type")
val token_type: String,
#SerializedName("refresh_token")
val refresh_token: String,
#SerializedName("access_token")
val access_token: String
) {
fun toUserSession(): UserSession = UserSession(
expiresIn = expires_in,
tokenType = token_type,
refreshToken = refresh_token,
accessToken = access_token
)
}
UserSession in domain:
data class UserSession(
val expiresIn:Int,
val tokenType:String,
val refreshToken:String,
val accessToken:String
)
LoginRepositoryImpl where the error occurs:
class LoginRepositoryImpl(private val apiService: ApiService) : LoginRepository {
override suspend fun login(username:String,password:String): UserSession? = withContext(Dispatchers.IO){
val response = apiService.getLoginResult(NetUser(username,password)).awaitResponse()
println("THE RESPONSE WAS : ${response.body()}")
return#withContext if(response.isSuccessful) response.body()?.toUserSession() else null
}
}
LoggingInterceptor result after the 200-OK:
{"expires_in":3600,"token_type":"Bearer","refresh_token":"T1amGR21.IdKM.5ecbf91162691e15913582bf2662e0","access_token":"T1amGT21.Idup.298885bf38e99053dca3434eb59c6aa"}
Response.body() print result:
THE RESPONSE WAS : NetUserSession(expires_in=0, token_type=null, refresh_token=null, access_token=null)
Any ideas what I'm failing to see here?
After busting my head for hours, the solution was to simply change the model class's members from val to var like so :
data class NetUserSession(
#SerializedName("expires_in")
var expires_in: Int = 0,
#SerializedName("token_type")
var token_type: String? = null,
#SerializedName("refresh_token")
var refresh_token: String? = null,
#SerializedName("access_token")
var access_token: String? = null
) {
fun toUserSession(): UserSession = UserSession(
expiresIn = expires_in,
tokenType = token_type!!,
refreshToken = refresh_token!!,
accessToken = access_token!!
)
}

Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $ when using Retrofit2 which returns onFailure

I'm getting this error ** Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $**
gradles below which I used for retrofit
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.12.0'
Retrofit class
class RetrofitClient private constructor() {
val myApi: Api
companion object {
#get:Synchronized
var instance: RetrofitClient? = null
get() {
if (field == null) {
field = RetrofitClient()
}
return field
}
private set
}
init {
val gson = GsonBuilder()
.setLenient()
.create()
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val client : OkHttpClient = OkHttpClient.Builder().addInterceptor(interceptor).build()
val retrofit: Retrofit = Retrofit.Builder().baseUrl("http://ctyf.co.in/api/")
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
myApi = retrofit.create(Api::class.java)
}
}
Api interface
public interface Api {
#Headers("Content-Type: application/json")
#GET("companyvehiclelatestinfo?token=F934A0C439/")
fun getLatLngs(): Call<ResponseLoc?>?
}
data class
data class ResponseLoc(
val Vehicle: List<Vehicle>
)
data model
data class Vehicle(
val Angle: String,
val Date: String,
val Ignition: String,
val Imei: String,
val Lat: String,
val Location: String,
val Long: String,
val Speed: String,
val Tempr: String,
val VehicleNo: String
)
finally calling here
private fun getLatLngs() {
val call: Call<ResponseLoc?>? = RetrofitClient.instance!!.myApi.getLatLngs()
call!!.enqueue(object : Callback<ResponseLoc?> {
override fun onResponse(call: Call<ResponseLoc?>, response: Response<ResponseLoc?>) {
val responseLoc: List<ResponseLoc> = response.body() as List<ResponseLoc>
//Creating an String array for the ListView
val data = arrayOfNulls<String>(responseLoc.size)
for (i in responseLoc.indices) {
data[i] = responseLoc[i].Vehicle.toString()
Log.d("apiii", data[i].toString())
}
}
override fun onFailure(call: Call<ResponseLoc?>, t: Throwable) {
Log.d("apii", t.message.toString())
}
})
}
JSON values
{"Vehicle":[{"VehicleNo":"Test","Imei":"354019","Location":"Tamil Nadu-India","Date":"2021-03-17 19:27:12.000","Tempr":"0","Ignition":"","Lat":"13.11","Long":"80.282","Speed":"0","Angle":"0"}]}
I have tried many stacks none of them helped
Is there any other ways available except Retrofit ???
anybody please help me to get the api results
I have tried many stacks none of them helped
Is there any other ways available except Retrofit ???
anybody please help me to get the api results

I am using Retrofit 2.0. I want to handle all types of network errors and exceptions

my application crashes when I have no internet connection : I am looking for a method that handles any exception form the retrofit instance like server is not found exception Timeout No internet connection
RequestRepository : my repository which contain all my functions
class RequestRepository {
/** suspend function to get the result of token request*/
suspend fun getToken(userLoginModel: UserLoginModel): Response<TokenResponse> {
return ApiService.APILogin.getToken(userLoginModel)
}
ApiService : contain my Retofit instance
object ApiService {
private var token: String = ""
fun setToken(tk: String) {
token = tk
}
private val okHttpClient = OkHttpClient.Builder().connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS).addInterceptor { chain ->
val chainRequest = chain.request()
val requestBuilder = chainRequest.newBuilder()
.addHeader("authorization", "Token $token")
.method(chainRequest.method, chainRequest.body)
val request = requestBuilder.build()
chain.proceed(request)
}.build()
var gson = GsonBuilder()
.setLenient()
.create()
private val retrofit by lazy {
Retrofit.Builder()
.baseUrl(LOGIN_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient)
.build()
}
val API: WebServicesApi by lazy {
retrofit.create(WebServicesApi::class.java)
}
WebServicesApi : my interface which contain my requests
interface WebServicesApi {
/** get the token from the API*/
#POST("user/login/")
suspend fun getToken(#Body userLoginModel: UserLoginModel): Response<TokenResponse>
}
LoginViewModel : my viewModel class
class LoginViewModel(private val repository: RequestRepository) : ViewModel() {
var tokenResponse: MutableLiveData<Response<TokenResponse>> = MutableLiveData()
/** using coroutine in getToken function to get the token */
fun getToken(userLoginModel: UserLoginModel) {
viewModelScope.launch(Dispatchers.IO) {
val tResponse = repository.getToken(userLoginModel)
tokenResponse.postValue(tResponse)
Log.d(TAG, "getToken: ${userLoginModel.password}")
}
}
}
You can add a Interceptor for handle error like this:
class GlobalErrorInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
try {
val response = chain.proceed(request)
if (!response.isSuccessful) {
val statusCode = response.code
when (statusCode) {
//Your handle status code in here
}
}
return response
} catch (ex: IOException) {
// You can replace my code with your exception handler code
return Response.Builder().request(chain.request()).protocol(Protocol.HTTP_1_1)
.message("Can't connect!").code(500).body(
ResponseBody.create(
"application/json; charset=utf-8".toMediaTypeOrNull(),
""
)
).build()
}
}
}
And you must add this class to OkHttpBuider:
val httpBuilder = OkHttpClient.Builder()
......
httpBuilder.addInterceptor(GlobalErrorInterceptor())

Android Retrofit cannot parse RequestBody

I have trouble with a Retrofit call. I have a node js server which is working fine and I want to send a login request to it from Android.
The HTTP request expects a JSON request body. I try to create it with OkHttp3.
Here is my interface code:
interface ApiService {
#POST("/login-player")
suspend fun loginPlayer(#Body body: RequestBody): Call<PlayerApiModel>
}
Of course, I have more paths than this but first of all, I want this to work.
My model class, if anyone interested:
data class PlayerApiModel(
#SerializedName("player_id") #Expose var playerId: Int,
#SerializedName("_id") #Expose var id: String,
#SerializedName("player_name") #Expose var name: String,
#SerializedName("password_hash") #Expose var password: String,
#SerializedName("_v") #Expose var version: Int
)
This is exactly the same schema as I get from the node server. But the problem is the request cannot even reach the server.
My Retrofit singleton looks like this:
class RetrofitInstance {
companion object {
private const val URL = "https://pedro.sch.bme.hu/"
val retrofit: ApiService by lazy {
val interceptor = HttpLoggingInterceptor()
val httpClient = OkHttpClient.Builder()
.addInterceptor(interceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.followRedirects(true)
.followSslRedirects(true)
.addInterceptor { chain ->
val newRequest = chain.request().newBuilder()
.addHeader("Authorization", UUID.randomUUID().toString())
.build()
chain.proceed(newRequest)
}
val builder = Retrofit.Builder()
.baseUrl(URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addCallAdapterFactory(SimpleCallAdapterFactory.create())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.addConverterFactory(MoshiConverterFactory.create())
val retrofit = builder
.client(httpClient.build())
.build()
retrofit.create(ApiService::class.java)
}
}
}
And lastly here is the problematic code part: the call.
val playerName = bind.root.txtPlayerName.text.toString()
val password = bind.root.txtPassword.text.toString()
val jsonObject = JSONObject()
jsonObject.put("player_name", playerName)
jsonObject.put("password", password)
val jsonBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), jsonObject.toString())
RetrofitInstance.retrofit.loginPlayer(jsonBody)
.enqueue(object : Callback<PlayerApiModel> {
override fun onResponse(call: Call<PlayerApiModel>, response: Response<PlayerApiModel>) {
println("${response.code()}: ${response.message()}")
when (response.code()) {
200 -> {
listener.goToMenu(playerName)
}
400 -> {
enableEditTexts()
Snackbar.make(bind.root, response.message(), Snackbar.LENGTH_LONG).show()
}
500 -> {
enableEditTexts()
serverErrorSnackbar()
}
}
}
override fun onFailure(call: Call<PlayerApiModel>, t: Throwable) {
Log.i("LoginViewModel::login()", t.message)
enableEditTexts()
}
})
The exception that I get comes to the loginPlayer() call.
This is the complete stack trace:
E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-1
Process: neptun.jxy1vz.cluedo, PID: 14287
java.lang.IllegalArgumentException: Unable to create call adapter for class java.lang.Object
for method ApiService.loginPlayer
at retrofit2.ServiceMethod$Builder.methodError(ServiceMethod.java:755)
at retrofit2.ServiceMethod$Builder.createCallAdapter(ServiceMethod.java:240)
at retrofit2.ServiceMethod$Builder.build(ServiceMethod.java:165)
at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:170)
at retrofit2.Retrofit$1.invoke(Retrofit.java:147)
at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
at $Proxy1.loginPlayer(Unknown Source)
at neptun.jxy1vz.cluedo.ui.activity.login.LoginViewModel$login$1.invokeSuspend(LoginViewModel.kt:57)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Caused by: java.lang.IllegalArgumentException: Could not locate call adapter for class java.lang.Object.
Tried:
* retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
* neptun.jxy1vz.cluedo.network.call_adapter.SimpleCallAdapterFactory
* retrofit2.ExecutorCallAdapterFactory
at retrofit2.Retrofit.nextCallAdapter(Retrofit.java:241)
at retrofit2.Retrofit.callAdapter(Retrofit.java:205)
at retrofit2.ServiceMethod$Builder.createCallAdapter(ServiceMethod.java:238)
... 12 more
I/Process: Sending signal. PID: 14287 SIG: 9
Update:
I got a suggestion to remove RxJava2CallAdapterFactory from Retrofit.
Now the bottom of the stack trace is this:
Tried:
* neptun.jxy1vz.cluedo.network.call_adapter.SimpleCallAdapterFactory
* retrofit2.ExecutorCallAdapterFactory
at retrofit2.Retrofit.nextCallAdapter(Retrofit.java:241)
at retrofit2.Retrofit.callAdapter(Retrofit.java:205)
at retrofit2.ServiceMethod$Builder.createCallAdapter(ServiceMethod.java:238)
... 12 more
You can see here my SimpleCallAdapter, which is pretty simple (haha), I wrote this according to a tutorial, but I did not do any special thing in it:
class SimpleCallAdapterFactory private constructor() : CallAdapter.Factory() {
override fun get(returnType: Type?, annotations: Array<out Annotation>?, retrofit: Retrofit?): CallAdapter<*, *>? =
returnType?.let {
return try {
// get enclosing type
val enclosingType = (it as ParameterizedType)
// ensure enclosing type is 'Simple'
if (enclosingType.rawType != Simple::class.java)
null
else {
val type = enclosingType.actualTypeArguments[0]
SimpleCallAdapter<Any>(type)
}
} catch (ex: ClassCastException) {
null
} }
companion object {
#JvmStatic
fun create() = SimpleCallAdapterFactory()
}
}
class SimpleCallAdapter<R>(private val responseType: Type): CallAdapter<R, Any> {
override fun responseType(): Type = responseType
override fun adapt(call: Call<R>): Any = Simple(call)
}
class Simple<R>(private val call: Call<R>) {
fun run(responseHandler: (R?, Throwable?) -> Unit) {
// run in the same thread
try {
// call and handle response
val response = call.execute()
handleResponse(response, responseHandler)
} catch (t: IOException) {
responseHandler(null, t)
}
}
fun process(responseHandler: (R?, Throwable?) -> Unit) {
// define callback
val callback = object : Callback<R> {
override fun onFailure(call: Call<R>?, t: Throwable?) =
responseHandler(null, t)
override fun onResponse(call: Call<R>?, r: Response<R>?) =
handleResponse(r, responseHandler)
}
// enqueue network call
call.enqueue(callback)
}
private fun handleResponse(response: Response<R>?, handler: (R?, Throwable?) -> Unit) {
response?.let {
if (response.isSuccessful) {
handler(response.body(), null)
println(response.body())
}
else {
println("${response.code()}: ${response.message()}")
if (response.code() in 400..511)
handler(null, HttpException(response))
else
handler(response.body(), null)
}
}
}
}
And the usage of this:
RetrofitInstance.retrofit.loginPlayer(jsonBody).process { playerApiModel, throwable ->
println("Debug: ${playerApiModel?.name}")
}
And in the interface:
interface ApiService {
#POST("/login-player")
suspend fun loginPlayer(#Body body: RequestBody): Simple<PlayerApiModel>
}
End of update
Curious that it does not recognize the RequestBody type and instead it tries to pass java.lang.Object.
I have all the necessary dependencies in Gradle.

Categories

Resources