Retrofit2 POST request with body as parameter Kotlin - kotlin

I'm new to Kotlin and having trouble to change the existing POST request parameters to body instead. I looked at other answers but none of them have the similar code as mine for the request part. I don't know how to change it just getting a lot of syntax errors. Thanks!
import retrofit2.Call
import retrofit2.http.*
interface PostInterface {
#POST("signin")
fun signIn(#Query("email") email: String, #Query("password") password: String): Call<String>
}
class BasicRepo #Inject constructor(val postInterface: PostInterface) {
fun signIn(email: String, password: String): MutableLiveData<Resource> {
val status: MutableLiveData<Resource> = MutableLiveData()
status.value = Resource.loading(null)
postInterface.signIn(email, password).enqueue(object : Callback<String> {
override fun onResponse(call: Call<String>, response: Response<String>) {
if (response.code() == 200 || response.code() == 201) {
// do something
} else {
// do something
}
}
}
}
}
class User constructor(
email: String,
password: String
)

#POST("signin")
suspend fun signIn(
#Body body: User,
): ResponseBody
Btw, You can use body instead of query params only if your API supports it.
Also, I recommend using a ResultWrapper. Handling errors with Retrofit and Coroutines in a single place

Related

Retrofit response body returning null

I've been stuck with this problem for some time now, and I can't seem to find the problem especially when all I did was following a guide online.
I'm trying to make a POST request, and receive a response in exchange:
Request body:
{
"Email":"test#gmail.com",
"firebaseUid":"Test_UID",
"IsBanned":1
}
Response body:
`
{
"Email": "test#gmail.com",
"UserId": 7
}
So basically whenever I submit a request to /users/ to create an account, I get both the email and UserId returned.
data class UserLogin(
#SerializedName("Email") val Email: String,
#SerializedName("UserId") val UserId: Int?,
#SerializedName("IsBanned") val IsBanned: Boolean?,
#SerializedName("firebaseUid") val firebaseUid: String?
)
object ServiceBuilder {
private val client = OkHttpClient.Builder().build()
private val retrofit = Retrofit.Builder()
.baseUrl("http://10.0.2.2/8000/")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
fun<T> buildService(service: Class<T>): T{
return retrofit.create(service)
}
}
class RestApiService {
fun addUser(userData: UserLogin, onResult: (UserLogin?) -> Unit){
val retrofit = ServiceBuilder.buildService(RestApi::class.java)
retrofit.addUser(userData).enqueue(
object : Callback<UserLogin> {
override fun onFailure(call: Call<UserLogin>, t: Throwable) {
Log.d("Failed retrofit",t.message.toString())
onResult(null)
}
override fun onResponse( call: Call<UserLogin>, response: Response<UserLogin>) {
val addedUser = response.body()
onResult(addedUser)
}
}
)
}
}
onFailure doesn't seem to be printing anything on the console. I'm calling the API from a button like this and both Email and UserId keep returning null for some reason:
`
val apiService = RestApiService()
val userInfo = UserLogin(UserId = null,firebaseUid = "TestTestTest", IsBanned = false, Email = "test#gmail.com");
apiService.addUser(userInfo){
Log.d("Retrofit user added", it?.Email.toString())
}
`
I tried to:
Set default values for the data class members.
Tried to check if response is successfull, and then print the errorBody if not. That didn't help either. I'm getting unreadable errors like $1#9afe35d instead.
Everything seem to be working fine when I do requests manually with POSTMAN.
It turned out there was nothing with the code. I just typed the port the wrong way and used a / instead of :
private val retrofit = Retrofit.Builder()
.baseUrl("http://10.0.2.2:8000/") // this
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()

Retrofit response with internal error with code 500

trying to use post/put call method in my Kotlin frontend to get response from Django backend. Method Get works but if I use ResponseBody nothing happens and log gives error with code 500.
Can someone help me?
This is my Api interface, where PUT and POST method doesnt work
public interface Api {
#GET("api/users")
fun getUsers(): Call<UserResults>
#PUT("/api/users/{Id}")
suspend fun updateUser(#Body requestBody: RequestBody, #Path("Id") userId: String): Response<ResponseBody>
#POST("/api/users")
suspend fun createUser(#Body requestBody: RequestBody): Response<ResponseBody>
}
This is function for example for PUT method
private fun updateSettings(preferredBranch: String) {
// Create Retrofit
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.build()
// Create Service
val service = retrofit.create(Api::class.java)
// Create JSON using JSONObject
val jsonObject = JSONObject()
jsonObject.put("preferred_branch", preferredBranch)
//jsonObject.put("job", "iOS Developer")
// Convert JSONObject to String
val jsonObjectString = jsonObject.toString()
// Create RequestBody ( We're not using any converter, like GsonConverter, MoshiConverter e.t.c, that's why we use RequestBody )
val requestBody = jsonObjectString.toRequestBody("application/json".toMediaTypeOrNull())
CoroutineScope(Dispatchers.IO).launch {
// Do the PUT request and get response
val response = service.updateUser(requestBody, logId.toString())
withContext(Dispatchers.Main) {
if (response.isSuccessful) {
// Convert raw JSON to pretty JSON using GSON library
val gson = GsonBuilder().setPrettyPrinting().create()
val prettyJson = gson.toJson(
JsonParser.parseString(
response.body()
?.string() // About this thread blocking annotation : https://github.com/square/retrofit/issues/3255
)
)
Log.d("Pretty Printed JSON :", "test")
} else {
Log.e("RETROFIT_ERROR", response.code().toString())
}
}
}
}

How to return an object from an api-call-function in Kotlin? [duplicate]

How can I return a value after a callback in kotlin, I tried using Thread.sleep but it doesn't work
fun searchColorFromAPI(): Colors {
val service: RetrofitService = ServiceGenerator.createService(RetrofitService::class.java)
val result: MutableList<String> = arrayListOf()
val call: Call<Colors?>? = service.unityConverter(result)
call?.enqueue(object : Callback<Colors?> {
override fun onResponse(call: Call<Colors?>?, response: Response<Colors?>) {
//switchProgressVisibility()
if (response.isSuccessful) {
val serviceResponse: Colors? = response.body()
if (serviceResponse != null) {
mColors = serviceResponse
}
else {
//buildToast(getString(R.string.null_response))
}
}
else {
//buildToast(getString(R.string.response_unsuccessful))
val errorBody: ResponseBody = response.errorBody()
Log.e(TAG, errorBody.toString())
}
}
override fun onFailure(call: Call<Colors?>?, t: Throwable?) {
/* buildToast(getString(R.string.error_calling_service))
Log.e(TAG, t?.message)*/
}
})
return mColors
}
Always, the mColors is returned before the onFailure or onResponse because they're asynchronous. Before this code was in MainActivity but I was advised to take off, but now when I try get mColors I get the empty value before and after the onResponse is executed, please I'm still learning Kotlin and Android.
Your problem stems from the fact that Retrofit call is asynchronous, so as soon as you call searchColorFromAPI it returns you mColors but the API call may not have been made yet, so you get the mColors value before API call.
To solve this issue, you can do
Use callback, this will require little modification in your current setup, but the 2nd option is preferable over this. Using callback your function should look like this.
/* Now instead of returning a value, your function takes a function (named callback)
as parameter. when your api call finishes, you can call the callback function and
pass the api response.
*/
fun searchColorFromAPI(callback: (Colors?) -> Unit) {
val service: RetrofitService = ServiceGenerator.createService(RetrofitService::class.java)
val result: MutableList<String> = arrayListOf()
val call: Call<Colors?>? = service.unityConverter(result)
call?.enqueue(object : Callback<Colors?> {
override fun onResponse(call: Call<Colors?>?, response: Response<Colors?>) {
//switchProgressVisibility()
if (response.isSuccessful) {
val serviceResponse: Colors? = response.body()
/** pass API response to callback */
callback(serviceResponse)
}
else {
val errorBody: ResponseBody = response.errorBody()
Log.e(TAG, errorBody.toString())
callback(null)
}
}
override fun onFailure(call: Call<Colors?>?, t: Throwable?) {
callback(null)
}
})
}
And in your activity declare a function as follows.
// This function will be called when your api call finishes
// and it will give you the api response
fun apiCallback(colors: Colors?){
if(colors == null){
// API Call failed
}
else{
// use colors as returned by API
}
}
And now call to searchColorFromApi should look like this
searchColorFromApi(apiCallback)
Use Live Data, declare following field in your viewmodel, if you are not using viewmodel then declare it in the class which has searchColorFromApi function.
var colors: MutableLiveData<Colors> = MutableLiveData()
and modify your searchColorFromAPI function as follows
fun searchColorFromAPI() {
val service: RetrofitService = ServiceGenerator.createService(RetrofitService::class.java)
val result: MutableList<String> = arrayListOf()
val call: Call<Colors?>? = service.unityConverter(result)
call?.enqueue(object : Callback<Colors?> {
override fun onResponse(call: Call<Colors?>?, response: Response<Colors?>) {
//switchProgressVisibility()
if (response.isSuccessful) {
val serviceResponse: Colors? = response.body()
if (serviceResponse != null) {
colors.postValue(response.body)
}
}
else {
colors.postValue(null)
val errorBody: ResponseBody = response.errorBody()
Log.e(TAG, errorBody.toString())
}
}
override fun onFailure(call: Call<Colors?>?, t: Throwable?) {
colors.postValue(null)
}
})
}
and in your activity do following
fun setupObservers(){
yourApiCallingClass.colors.observe(this, Observer {
// this code is called when ever value of color field changes
})
}
You can use live data ,that gets updated once the callback receives ,the same live data is observed by the caller fragment/activity
You can use coroutines to return a value from function which has asyn calls in it.
You can use interface callbacks to activity/ fragment to trigger the updates received from retrofit calls.

Retrofit 2 Get Github Users API always returning null

I tried to get json from the https://github.com/users.
I want to show username : yehezkiell like https://github.com/yehezkiell.
The retrofit showing success result, but its always return null. I'm new in this retrofit, please help
this my code
val postService = DataRepository.create()
postService.getUser("yehezkiell").enqueue(object : Callback<Users>{
override fun onFailure(call: Call<Users>?, t: Throwable?) {
Log.e("retrofitnya","gagal ${t}")
}
override fun onResponse(call: Call<Users>?, response: Response<Users>?) {
Log.e("retrofitnya","berhasil")
val data = response?.body()
Log.e("retrofitnya","berhasil ${data?.name}")
}
})
Retrofit Instance
interface RetrofitInstance {
#GET("users/{username}")
fun getUser(#Path("username") username:String ): Call<Users>
}
Data repo
object DataRepository {
fun create(): RetrofitInstance {
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://github.com")
.build()
return retrofit.create(RetrofitInstance::class.java)
}
}
Users.kt
open class Users {
#SerializedName("name")
#Expose
open var name: String? = null
#SerializedName("username")
#Expose
open var username: String? = null
#SerializedName("email")
#Expose
open var email: String? = null
}
For debugging process, instead of de-serialization to Users object immediately after response, should we do somethings like these? :
De-serialize it to plain string first.
interface RetrofitInstance {
#GET("users/{username}")
fun getUser(#Path("username") username: String): Call<String>
}
Just log that string to show what we really get.
override fun onResponse(call: Call<String>?, response: Response<String>?) {
val responseBody = response?.body() ?: ""
Log.e("retrofitnya","response body as string = ${responseBody}")
}
(If we want to use it as Users after that) do manually de-serialize it.
val user: Users = Gson().fromJson(responseBody, Users::class.java)
If it is not too confidential, plz give us how you declare that Users data object like, for example, this Foo and Bar.
data class Foo(
#SerializedName("bar") val bar: Bar?
)
data class Bar(
#SerializedName("name") val name: String?
)
I solved this by myself, actually its my silly miss understanding which is that end point is wrong.
In my wrong code
object DataRepository {
fun create(): RetrofitInstance {
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://github.com")
.build()
return retrofit.create(RetrofitInstance::class.java)
}
}
That wrong end point is
https://github.com
The true one is
https://api.github.com/

RXJava2 on Android - nested completable does not get called

I have a completable method for authentication and I want to retrieve the authenticated user details on login success.
After retrieving them, I want to call an onUserAuthenticated method.
I am doing this with a nested completable (2 levels deep), as I want to sent both the authorization token received on login and the user details to the onUserAuthenticated method.
The problem is that onUserAuthenticated never gets invoked.
class LoginViewModel(val emailAuthentication: EmailAuthentication) : ViewModel() {
val email = MutableLiveData<String>()
val password = MutableLiveData<String>()
fun login() {
emailAuthentication.login(email = email.value!!, password = password.value!!)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Timber.d("User $email logged in")
}, { error ->
Timber.e(error, "Error logging in $email")
})
}
}
class EmailAuthenticationImpl(private val authentication: Authentication,
private val userRepository: UserRepository,
private val authRepository: AuthenticationRepository
) : EmailAuthentication {
override fun register(email: String, password: String): Completable {
return userRepository.register(email, password)
}
override fun login(email: String, password: String): Completable {
// some missing fields validation
return authRepository.login(email, password)
.flatMapCompletable { token ->
userRepository.getCurrentUser()
.flatMapCompletable {
Completable.defer {
// FIXME this never gets invoked
authentication.onUserAuthenticated(AuthType.EMAIL, it, token)
Completable.complete()
}
}
}
}
I tried putting Completable.defer also only before userRepository.getCurrentUser() and both before userRepository.getCurrentUser() and before authentication.onUserAuthenticated(AuthType.EMAIL, it, token), but the code is never reached.
What am I doing wrong?