I am trying to make a simple post request to googles dialogflow in retrofit using kotlin. I am modeling my code off of this site. However, I keep getting 400 errors when trying to make a search so there must be something wrong with my interface creating the message body I believe. I have working python code that does the same functionality as shown here:
url = "https://api.dialogflow.com/v1/query?v=20170712"
headers = {
'Authorization': 'Bearer ' + my_key ,
'Content-Type' : 'application/json'
}
body = {
'lang': 'en',
'query': 'id like to fix my wire c1000 stocks',
'sessionId': 'me'
}
resp = r.post(url,headers=headers,data=json.dumps(body))
I have set this up in android studio as 3 classes:
1) Message.kt
The body of the post request
object Message {
data class MsgBody(val lang: String, val query: String, val sesId: String)
}
2) Model.kt
The response from dialogflow
object Model {
data class Response(val resp: Result)
data class Result(val fulfillment: Fulfillment)
data class Fulfillment(val speech: String)
}
3) DialogFlowService.kt
The interface that has the post request enpoint
interface DialogFlowService {
#Headers(
"Authorization: Bearer {MY API KEY}",
"Content-Type: application/json"
)
#POST("query")
fun getAiMessage(#Body msg: Message.MsgBody,
#Query("v") v: String): Observable<Model1.Response>
companion object {
fun create(): DialogFlowService {
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://api.dialogflow.com/v1/")
.build()
return retrofit.create(DialogFlowService::class.java)
}
}
}
All of this is then used in my main activity as seen below:
class MainActivity : AppCompatActivity() {
private var disposable: Disposable? = null
private val dialogFlowService by lazy {
DialogFlowService.create()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
if (editText.text.toString().isNotEmpty()) {
sendMessage(editText.text.toString())
}
}
}
private fun sendMessage(msg: String){
disposable = dialogFlowService.getAiMessage(Message.MsgBody("en",msg,"me"),"20170712")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result -> textView.text = "DialogFlow says: ${result.resp.fulfillment.speech}" },
{ error -> Toast.makeText(this, error.message, Toast.LENGTH_SHORT).show() }
)
}
override fun onPause() {
super.onPause()
disposable?.dispose()
}
}
I tried to follow the tutorial as close as possible and am very confused as to what I did wrong. Like I said above I think this is related to my DialogFlowService.kt file. Thanks for any help in advance.
Error in post request since Model variable name sesId did not equal the actual key sessionId. As Raghunandan said a logging interceptor is very useful
Related
I've tried following several tutorials on calling an API and receiving a JSON response in Kotlin, and this is what I have so far:
interface APIService {
#GET("cursor/popular/10")
fun listRepos(): Call<Any?>
// #Path("user") user: String?
}
fun getURL(url: String): Call<Any?> {
val retrofit = Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build()
val service = retrofit.create(APIService::class.java)
val data: Call<Any?> = service.listRepos()
Log.d("PRINT_JSON_HERE", "HERE: ${data}")
return data
}
When I call getURL("api_here") nothing happens, no error either.
Just wondering what I am doing wrong. I know it says the data class is "Any" but when I start getting a response I'll replace it with a proper data class.
This code is inside a class/ViewModel(). What am I doing wrong?
Api call returns a response and you need to implement onResponse and onFailure override method in your program after
val service = retrofit.create(APIService::class.java)
this put this code
service.enqueue(object : Callback<List<userItem>> {
override fun onResponse(
call : Call<List<userItem>>,
response: Response<List<userItem>>
) {
var data = response.body()
Log.d("data", data.toString)
}
override fun onFailure(call : Call<List<userItem>> , t : Throwable) {
"print toast if an error occurred"
}
)}
}
}
in this code userItem is a data class that can get data from the api.
I hope this can help you.
):
i am new to kotlin and i am in learning phase. I have followed many links but didn't able to understand completely.
I want Json response to show in my textview.
Problem: 1
I have tried this code but was unable to get data, but i want to get the items inside data object. Quote and author are coming null.
{
"status": 200,
"message": "Success",
"data": {
"Quote": "The pain you feel today will be the strength you feel tomorrow.",
"Author": ""
},
"time": "0.14 s"
}
Problem: 2
I dont know how to parse this response in textview
object ServiceBuilder {
private val client = OkHttpClient.Builder().build()
private val retrofit = Retrofit.Builder()
.baseUrl("https://url.com.pk/") // change this IP for testing by your actual machine IP
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
fun<T> buildService(service: Class<T>): T{
return retrofit.create(service)
}}
RestApi
interface RestApi{
#Headers("Content-Type: application/json")
#POST("api/getquotes")
abstract fun addUser(#Body userData: UserInfo): Call<UserInfo>}
RestAPiService
class RestApiService
{
fun addUser(userData: UserInfo, onResult: (UserInfo?) -> Unit)
{
val retrofit = ServiceBuilder.buildService(RestApi::class.java)
retrofit.addUser(userData).enqueue(
object : Callback<UserInfo>
{
override fun onFailure(call: Call<UserInfo>, t: Throwable)
{
onResult(null)
}
override fun onResponse( call: Call<UserInfo>, response: Response<UserInfo>)
{
val addedUser = response.body()
Log.d("responsee",""+addedUser)
onResult(addedUser)
}
}
)
}
}
UserInfo
data class UserInfo (
#SerializedName("Quote")
val quote : String,
#SerializedName("Author")
val author : String
)
MainActivity
fun getQuotes() {
val apiService = RestApiService()
val userInfo = UserInfo("","")
apiService.addUser(userInfo) {
Log.d("Error registering user","errter")
/*if ( != null)
{
// it = newly added user parsed as response
// it?.id = newly added user ID
} else {
Log.d("Error registering user","errter")
}*/
}
}
Any help would be appreciated :)
Status, message and data are all part of the response so you need to take care of that. For example this
data class AddUserResponse(
val `data`: UserInfo, //like you defined it
val message: String,
val status: Int,
val time: String
)
This means parameter and response are different so the RestApi needs to be changed to this
abstract fun addUser(#Body userData: UserInfo): Call<AddUserResponse>}
This in turn also change the types in the service like
class RestApiService
{
fun addUser(userData: UserInfo, onResult: (UserInfo?) -> Unit)
{
val retrofit = ServiceBuilder.buildService(RestApi::class.java)
retrofit.addUser(userData).enqueue(
object : Callback<AddUserResponse>
{
override fun onFailure(call: Call<AddUserResponse>, t: Throwable)
{
onResult(null)
}
override fun onResponse( call: Call<AddUserResponse>, response: Response<AddUserResponse>)
{
val addedUser = response.body()
Log.d("responsee",""+addedUser)
onResult(addedUser.data)
}
}
)
}
}
now in getQuotes you will have that it is a UserInfo object
apiService.addUser(userInfo) {
val returnedUserInfo = it
}
just follow my steps :
File->settings->Plugins
search for JSON To Kotlin class and install it
again click on File->New->Kotlin Data class from JSON
paste your json code here and click on generate. It will generate POJO classes and you will good to go.
The first thing I noticed, is that the data in your json is:
"Quote": "The pain you feel today will be the strength you feel tomorrow.",
"Author": ""
While your UserInfo defined #SerializedName("message") for Quote.
Hello I am working on async using retrofit and rxjava2
and I have to get the value from the header while talking to the server developer.
However, I don't know how to get the header from the method I use. I know how to get it from Call Response, but I don't know how to bring the header because the method used is different.
my retrofit2 class
private val retrofit: Retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(client())
.baseUrl(serverIp)
.build()
val userApi: UserAPI = retrofit.create(UserAPI::class.java)
my model class
#POST("user")
fun login(
#Body loginRequest : LoginRequest
) : Single<UserResponse>
data class LoginRequest(
val phone: String?,
#SerializedName("gender")
val gender: String?,
#SerializedName("age")
val age: String?,
#SerializedName("email")
val email: String?
)
data class UserResponse (
override var status: Int,
override var message: String,
override var timestamp: String,
var data: Data
) : CommonResponse() {
data class Data(
var username: String?,
var token: String?
)
}
my viewModel ( rx )
addDisposable(
model.loginBody(loginRequest)
.subscribeOn(Schedulers.io())
.subscribe({
_loginResult.postValue(it)
}, {
Timber.d("response error, message : ${it.localizedMessage}")
})
)
My current situation is as follows. I need the headers returned by the server after login,
I can see it from the okhttp log, but I don't know how to get a specific header
Word of advice rather than a solution
Not a question of solving your problem but even if it's late, as you're
using Kotlin, a better solution would be to migrate from rxJava to Kotlin Flow. It allows you to use suspend functions to do your retrofit calls, then use Kotlin Flow to do the rxJava's job on the IO thread.
It also allows you to use the Response<T> retrofit object in a simpler way.
Example:
The retrofit request
#POST(YOUR_ROAD) // Retrofit road
suspend fun connect(
#Body credentials: CredentialsObjectBody
): Response<ConnectionObjectResponse>
The repository call to the retrofit request
// The function is suspending the thread with suspend keyword
suspend fun yourFunction(): Flow<DataState<TypeForEmitter>> = flow {
try {
body.clientId = sessionPrefs.deviceName!!
val connectionResponse =
majorApi.connect(
// what you need to put in the body
)
.apply {
if (isSuccessful) {
body()?.let {
// Here you can use what's in the request body
emit(DataState.Success(it))
} ?: throw Exception("Request body was null")
} else {
throw Exception(headers()["message"]) // use throw to handle the error in the catch
}
}
} catch (e: Exception) {
Log.e(TAG, "error: ${e.message}")
emit(DataState.Error(e))
}
}
DataState:
DataState is a sealed class that allows to differentiate emitted status
sealed class DataState<out R> {
data class Success<out T>(val data: T) : DataState<T>()
data class Error(val exception: Exception) : DataState<Nothing>()
object Loading : DataState<Nothing>()
}
How to call the Kotlin flow to launch it on IO thread to prevent blocking the Main (or UI) thread
withContext(Dispatchers.IO) {
yourFunction().onEach {
/*
onEach is like onNext, but emits every type of error, compared to rxJava
that differentiates next/success, complete and error events
*/
}
}
To retreive response headers and other usefull information you can use the Response type from retrofit2 package. To use this change the return type of your login method to Single<retrofit2.Response<UserResponse>>
#POST("user")
fun login( #Body loginRequest : LoginRequest): Single<retrofit2.Response<UserResponse>>
Now to retrieve headers in your ViewModel
addDisposable(
model.loginBody(loginRequest)
.subscribeOn(Schedulers.io())
.subscribe({
val headers = it.headers() // do something with headers
val data = it.body()
_loginResult.postValue(data)
}, {
Timber.d("response error, message : ${it.localizedMessage}")
})
)
I have a app whos call a service POST (postman test) and get some information from that call.
My retrofit initializer:
class RetrofitInitializer {
private val retrofit = Retrofit.Builder()
.baseUrl("https://bank-app-test.herokuapp.com/api/")
.addConverterFactory(GsonConverterFactory.create())
.build()
fun loginApiContract() : LoginApiContract{
return retrofit.create(LoginApiContract::class.java)
}
}
the interface:
interface LoginApiContract {
#POST("login")
fun login() : Call<UserAccount>
#GET("statements")
fun getStatements()
}
and finally the call:
val call = RetrofitInitializer().loginApiContract().login()
call.enqueue(object: Callback<UserAccount> {
override fun onResponse(call: Call<UserAccount?>?,
response: Response<UserAccount?>?) {
response?.body()?.let {
val myUserAccount: UserAccount = it
loginView.doLogin(myUserAccount)
}
}
override fun onFailure(call: Call<UserAccount?>?,
t: Throwable?) {
Log.e("onFailure error", t?.message)
}
})
I got response code 200, but response body is empty.
This is my postman response:
{
"userAccount": {
"userId": 1,
"name": "Jose da Silva Teste",
"bankAccount": "2050",
"agency": "012314564",
"balance": 3.3445
},
"error": {}
}
and this is my model:
class UserAccount constructor(var userId: Int, var name: String, var bankAccount: String, var agency: String, var balance: Double){
init{
this.userId = userId
this.name = name
this.bankAccount = bankAccount
this.agency = agency
this.balance = balance
}
}
I found!
Its a problem with my postman. For any reason my AVD cant access the mock server. now i solve my problem with a simple restful api node.js.
Thx for the help guys.
Your response contains elements named "userAccount" and "error". Your UserAccount class has neither which is causing the issue. Therefore, use Retrofit with a class like this:
data class UserResponse(val userAccount: UserAccount, val error: BackendError)
After configuring Kotlin for Android project, I wrote a simple MainActivity.kt. It called Retrofit to get a JSON file which contained the following data:
{
"message": "success",
"user": {
"username": "Eric"
}
}
Now I want to use Moshi to convert the JSON data to Kotlin's class, so here are the two classes to reflect the above JSON structure:
class User(var username: String)
class UserJson(var message: String, var user: User)
And a custom type adapter for Moshi:
class UserAdapter {
#FromJson fun fromJson(userJson: UserJson) : User {
Log.d("MyLog", "message = ${userJson.message}") // = success
Log.d("MyLog", "user = ${userJson.user}") // = null
return userJson.user
}
}
When it goes into the function fromJson(), userJson.message = "success" as expected. But the strange thing is that userJson.user is null, which should be User(username="Eric").
I am new to Moshi and Kotlin, and I have already stuck with this problem for about 10 hours. Please help me out. Thanks for any help.
========================================
The following is the entire code of MainActivity.kt (50 lines only):
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Custom Type Adapters for Moshi
val userMoshi = Moshi.Builder().add(UserAdapter()).build()
val retrofit = Retrofit.Builder()
.baseUrl("https://dl.dropboxusercontent.com/")
.addConverterFactory(MoshiConverterFactory.create(userMoshi))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
val accountService = retrofit.create(AccountService::class.java)
accountService.signUpAnonymously()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { user ->
Log.d("MyLog", user.toString())
}
}
}
// ========== For Retrofit ==========
interface AccountService {
#GET("u/17350105/test.json")
fun signUpAnonymously() : Observable<User>
}
// ========== For Moshi ==========
class User(var username: String)
class UserJson(var message: String, var user: User)
class UserAdapter {
#FromJson fun fromJson(userJson: UserJson) : User {
Log.d("MyLog", "message = ${userJson.message}") // = success
Log.d("MyLog", "user = ${userJson.user}") // = null
return userJson.user
}
}
The build.gradle is:
compile "io.reactivex.rxjava2:rxjava:2.0.0"
compile "io.reactivex.rxjava2:rxandroid:2.0.0"
compile "com.android.support:appcompat-v7:25.0.0"
compile "com.squareup.retrofit2:retrofit:2.1.0"
compile "com.squareup.retrofit2:converter-moshi:2.1.0"
compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
Thank you again.
You can solve the problem by changing your code to do something like below.
Basically in your case when the UserAdapter is registered, it tells moshi that it can create a User only from UserJson object. Hence Moshi does not recognize the JSON object with keyword user.
By adding an indirection in form of User1 (please pardon the naming convention), the UserJson is created properly with User1 from JSON.
class User(var username: String)
class User1(var username: String) // I introduced this class
class UserJson(var message: String, var user: User1) // changed User to User1
class UserAdapter {
#FromJson fun fromJson(userJson: UserJson): User {
println("message = ${userJson.message}")
println("user = ${userJson.user}")
return User(userJson.user.username)
}
}
If you just need the User object. There is a library called Moshi-Lazy-Adapters that provides a #Wrapped annotation, that allows specifying the path to the desired object. All you have to do is add the respective adapter to your Moshi instance and change the service code to:
interface AccountService {
#GET("u/17350105/test.json")
#Wrapped("user")
fun signUpAnonymously() : Observable<User>
}
No need for any other custom adapter.