io.ktor.client.features.ClientRequestException: Client request invalid - kotlin

When calling the getUser function, I make an HTTP request that retrieves information about a user.
If the user does not exist I get the error:
io.ktor.client.features.ClientRequestException: Client request(https://api.intra.chocolat.fr/v2/users/fff) invalid: 404 Not Found. Text: "{}"
I am unable to catch the error, and to continue the execution of the program.
package com.ksainthi.swifty.api
import android.util.Log
import androidx.annotation.Keep
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.features.*
import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
import io.ktor.client.features.observer.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.serialization.*
class Api42Service {
#Keep
#Serializable
data class Token(
#SerialName("access_token") val accessToken: String,
#SerialName("token_type") val tokenType: String,
#SerialName("expires_in") val expiresIn: Int,
#SerialName("scope") val scope: String,
#SerialName("created_at") val createdAt: Int
)
#Keep
#Serializable
data class User(
#SerialName("id") val id: Int,
#SerialName("email") val email: String,
#SerialName("login") val login: String? = null,
#SerialName("first_name") val firstName: String? = null,
#SerialName("last_name") val lastName: String? = null,
)
private var accessToken: String? = null
private var client: HttpClient = HttpClient() {
install(JsonFeature) {
serializer = KotlinxSerializer(kotlinx.serialization.json.Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
}
private val API_URL: String = "https://api.intra.chocolat.fr"
private val API_SECRET: String =
"<secret>"
private val API_UID: String =
"<api_uid>"
suspend fun requestAPI(
requestMethod: HttpMethod,
path: String,
params: MutableMap<String, String>?
): HttpResponse {
val urlString = API_URL
.plus(path)
return client.request(urlString) {
method = requestMethod
params?.forEach { (key, value) ->
parameter(key, value)
}
headers {
accessToken?.let {
println(it)
append(HttpHeaders.Authorization, "Bearer $it")
}
}
}
}
suspend fun getToken(): Token {
val params: MutableMap<String, String> = HashMap()
params["grant_type"] = "client_credentials";
params["client_secret"] = API_SECRET;
params["client_id"] = API_UID;
val response = requestAPI(HttpMethod.Post, "/oauth/token", params);
val token: Token = response.receive()
accessToken = token.accessToken
return token
}
suspend fun getUser(login: String): User? {
try {
val response = requestAPI(HttpMethod.Get, "/v2/users/$login", null);
val user: User = response.receive()
return user
} catch (cause: io.ktor.client.features.ClientRequestException) {
Log.d("wtf", "Catch your error here")
}
return null
}
}

Related

TestApplication in Ktor

So i have built my Ktor backend and im watching a tutorial from philip lackner Tutorial
He starts of with making a withTestApplication call within his testfunction... The thing is that this has been deprecated and it would be nice to use the newer version of it which i understand is
#Test
fun `Create user, no body attached, responds with BadRequest`() = testApplication{
application {
install(Routing) {
createUserRoute(userRepository)
}
}
}
And so i use this code but i have no clue how to get the scope so i can use handleRequest?
please can somebody help me with this?
This is the code i wanted to test
package com.PapperSeller.routes
import com.PapperSeller.controller.user.UserRepository
import com.PapperSeller.data.models.User
import com.PapperSeller.data.requests.CreateAccountRequest
import com.PapperSeller.data.responses.BasicApiResponse
import com.PapperSeller.util.ApiResponseMessages
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import org.koin.ktor.ext.inject
fun Route.createUserRoute(repository: UserRepository){
route("/api/user/create"){
post {
kotlin.runCatching { call.receive<CreateAccountRequest>()}.onSuccess {request ->
val userExist = repository.getUserByEmail(request.email) != null
if(userExist){
call.respond(BasicApiResponse(ApiResponseMessages.USER_ALREADY_EXIST, false))
return#post
}else{
if(request.email.isBlank() || request.username.isBlank() || request.password.isBlank()){
call.respond(BasicApiResponse(ApiResponseMessages.FIELDS_EMPTY, false))
return#post
}else{
repository.createUser(User(email = request.email, username = request.username, password = request.password))
call.respond(BasicApiResponse("", true))
return#post
}
}
}.onFailure {
call.respond(BasicApiResponse(ApiResponseMessages.NO_USER_REGISTERED, false))
return#post
}
}
}
}
Here is an example of how you can cover the Route.createUserRoute with tests:
import io.ktor.client.call.*
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.testing.*
import kotlinx.serialization.Serializable
import kotlin.test.Test
import kotlin.test.assertEquals
class KtorTest {
#Test
fun userNotRegistered() = testApplication {
application {
module(MemoryUserRepository())
}
val client = createClient {
install(ContentNegotiation) {
json()
}
}
assertEquals(
BasicApiResponse(ApiResponseMessages.NO_USER_REGISTERED, false),
client.post("/api/user/create").body()
)
}
#Test
fun userAlreadyExist() = testApplication {
application {
module(MemoryUserRepository(mapOf("john#doe" to User("", "", ""))))
}
val client = createClient {
install(ContentNegotiation) {
json()
}
}
client.post("/api/user/create") {
setBody(CreateAccountRequest("john#doe", "", ""))
contentType(ContentType.Application.Json)
}.body<BasicApiResponse>().let { response ->
assertEquals(BasicApiResponse(ApiResponseMessages.USER_ALREADY_EXIST, false), response)
}
}
#Test
fun emptyRequestFields() = testApplication {
application {
module(MemoryUserRepository())
}
val client = createClient {
install(ContentNegotiation) {
json()
}
}
client.post("/api/user/create") {
setBody(CreateAccountRequest("", "", ""))
contentType(ContentType.Application.Json)
}.body<BasicApiResponse>().let { response ->
assertEquals(BasicApiResponse(ApiResponseMessages.FIELDS_EMPTY, false), response)
}
}
#Test
fun successfulRegistration() = testApplication {
application {
module(MemoryUserRepository())
}
val client = createClient {
install(ContentNegotiation) {
json()
}
}
client.post("/api/user/create") {
setBody(CreateAccountRequest("john#doe", "john", "123"))
contentType(ContentType.Application.Json)
}.body<BasicApiResponse>().let { response ->
assertEquals(BasicApiResponse("", true), response)
}
}
}
fun Application.module(repository: UserRepository) {
install(io.ktor.server.plugins.contentnegotiation.ContentNegotiation) {
json()
}
routing {
createUserRoute(repository)
}
}
fun Route.createUserRoute(repository: UserRepository) {
route("/api/user/create") {
post {
kotlin.runCatching { call.receive<CreateAccountRequest>() }.onSuccess { request ->
val userExist = repository.getUserByEmail(request.email) != null
if (userExist) {
call.respond(BasicApiResponse(ApiResponseMessages.USER_ALREADY_EXIST, false))
return#post
} else {
if (request.email.isBlank() || request.username.isBlank() || request.password.isBlank()) {
call.respond(BasicApiResponse(ApiResponseMessages.FIELDS_EMPTY, false))
return#post
} else {
repository.createUser(
User(
email = request.email,
username = request.username,
password = request.password
)
)
call.respond(BasicApiResponse("", true))
return#post
}
}
}.onFailure {
call.respond(BasicApiResponse(ApiResponseMessages.NO_USER_REGISTERED, false))
return#post
}
}
}
}
class ApiResponseMessages {
companion object {
val USER_ALREADY_EXIST = "User does already exist"
val FIELDS_EMPTY = "Something missing"
val NO_USER_REGISTERED = "User isn't registered"
}
}
interface UserRepository {
fun getUserByEmail(email: String): User?
fun createUser(user: User)
}
class MemoryUserRepository(initialUsers: Map<String, User> = mapOf()): UserRepository {
private val users = initialUsers.toMutableMap()
override fun getUserByEmail(email: String): User? {
return users[email]
}
override fun createUser(user: User) {
users[user.email] = user
}
}
#Serializable
data class CreateAccountRequest(val email: String, val username: String, val password: String)
data class User(val email: String, val username: String, val password: String)
#Serializable
data class BasicApiResponse(val message: String, val success: Boolean)

Kotlin - Ktor - How to handle Optional API resource fields in PATCH calls?

When implementing a REST API with Ktor (and Kotlin), it supports the optional field handling of Kotlin. Which works for POST and GET, but what about PATCH (update)?
For example, you have the following resource:
#Serializable
data class MyAddress(
var line1: String? = null,
var line2: String? = null,
var city: String? = null,
var postal_code: String? = null,
var state: String? = null,
var country: String? = null
)
So all MyAddress fields are optional (with a default value).
When you create an address with POST:
"line1" : "line1",
"country" : "XX"
and you than want to remove the country with a PATCH:
"country" : null
the end result of the resource should be:
"line1" : "line1"
But how can you determine this by parsing the json of the PATCH request? Because there is no way, as far as I know, to determine if it was null by default, or submitted.
Furthermore, the default null value for the MyAddress is required, because else the parsing will not work.
Code example:
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
#kotlinx.serialization.Serializable
data class MyAddress(
var line1: String? = null,
var line2: String? = null,
var city: String? = null,
var postal_code: String? = null,
var state: String? = null,
var country: String? = null
)
fun main() {
val jsonStringPOST = "{\"line1\":\"street\",\"country\":\"GB\"}"
println("JSON string is: $jsonStringPOST")
val myAddressPost = Json.decodeFromString<MyAddress>(jsonStringPOST)
println("MyAddress object: $myAddressPost")
val jsonStringPATCH = "{\"country\":null}"
println("JSON string is: $jsonStringPATCH")
val myAddressPatch = Json.decodeFromString<MyAddress>(jsonStringPATCH)
println("MyAddress object: $myAddressPatch")
}
I tried to add Optional<String>? as well, but it complains about missing serialization of Optional, and preferably I do not want to make all my data var's Optionals.
Note: I am looking for a more structured solution, that also works with all other resources in the api (10+ classes).
A second solution, based on Aleksei's example:
#Serializable
data class Address2(val line1: OptionalValue<String> = Undefined, val country: OptionalValue<String> = Undefined)
#Serializable(with = OptionalValueSerializer::class)
sealed interface OptionalValue<out T>
object Undefined: OptionalValue<Nothing> {
override fun toString(): String = "Undefined"
}
object Absent: OptionalValue<Nothing> {
override fun toString(): String = "Absent"
}
class WithValue<T>(val value: T): OptionalValue<T> {
override fun toString(): String = value.toString()
}
open class OptionalValueSerializer<T>(private val valueSerializer: KSerializer<T>) : KSerializer<OptionalValue<T>> {
override val descriptor: SerialDescriptor = valueSerializer.descriptor
override fun deserialize(decoder: Decoder): OptionalValue<T> {
return try {
WithValue(valueSerializer.deserialize(decoder))
} catch (cause: SerializationException) {
Absent
}
}
override fun serialize(encoder: Encoder, value: OptionalValue<T>) {
when (value) {
is Undefined -> {}
is Absent -> { encoder.encodeNull() }
is WithValue -> valueSerializer.serialize(encoder, value.value)
}
}
}
fun main() {
val jsonStringPOST = "{\"line1\":\"street\",\"country\":\"GB\"}"
println("JSON string is: $jsonStringPOST")
val myAddressPost = Json.decodeFromString<Address2>(jsonStringPOST)
println("MyAddress object: $myAddressPost")
val jsonStringUPDATE = "{\"country\":null}"
println("JSON string is: $jsonStringUPDATE")
val myAddressUpdate = Json.decodeFromString<Address2>(jsonStringUPDATE)
println("MyAddress object: $myAddressUpdate")
if(myAddressUpdate.country is Absent || myAddressUpdate.country is WithValue) {
println("Update country: ${myAddressUpdate.country}")
} else {
println("No update for country: ${myAddressUpdate.country}")
}
}
Output is:
JSON string is: {"line1":"street","country":"GB"}
MyAddress object: Address2(line1=street, country=GB)
JSON string is: {"country":null}
MyAddress object: Address2(line1=Undefined, country=Absent)
Update country: Absent
You can use a sealed interface for a part of an address to represent undefined value, absence of value, and actual value. For this interface, you need to write a serializer that will encode and decode values accordingly to your logic. I'm not good at the kotlinx.serialization framework so I wrote an example as simple as possible.
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.request.*
import io.ktor.server.routing.*
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
fun main() {
embeddedServer(Netty, port = 4444) {
install(ContentNegotiation) {
json()
}
routing {
post {
val address = call.receive<Address>()
println(address)
}
}
}.start()
}
#Serializable
data class Address(val line1: MyValue = Undefined, val country: MyValue = Undefined)
#Serializable(with = AddressValueSerializer::class)
sealed interface MyValue
object Undefined: MyValue {
override fun toString(): String = "Undefined"
}
object Absent: MyValue {
override fun toString(): String = "Absent"
}
class WithValue(val value: String): MyValue {
override fun toString(): String = value
}
object AddressValueSerializer: KSerializer<MyValue> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("AddressValue", PrimitiveKind.STRING)
override fun deserialize(decoder: Decoder): MyValue {
return try {
WithValue(decoder.decodeString())
} catch (cause: SerializationException) {
Absent
}
}
#OptIn(ExperimentalSerializationApi::class)
override fun serialize(encoder: Encoder, value: MyValue) {
when (value) {
is Undefined -> {}
is Absent -> { encoder.encodeNull() }
is WithValue -> { encoder.encodeString(value.value) }
}
}
}
With some help from medium.com, I came to the following solution:
#Serializable(with = OptionalPropertySerializer::class)
open class OptionalProperty<out T> {
object NotPresent : OptionalProperty<Nothing>()
data class Present<T>(val value: T) : OptionalProperty<T>() {
override fun toString(): String {
return value.toString()
}
}
fun isPresent() : Boolean {
return this is Present
}
fun isNotPresent(): Boolean {
return this is NotPresent
}
fun isEmpty(): Boolean {
return (this is Present) && this.value == null
}
fun hasValue(): Boolean {
return (this is Present) && this.value != null
}
override fun toString(): String {
if(this is NotPresent) {
return "<NotPresent>"
}
return super.toString()
}
}
open class OptionalPropertySerializer<T>(private val valueSerializer: KSerializer<T>) : KSerializer<OptionalProperty<T>> {
override val descriptor: SerialDescriptor = valueSerializer.descriptor
override fun deserialize(decoder: Decoder): OptionalProperty<T> =
OptionalProperty.Present(valueSerializer.deserialize(decoder))
override fun serialize(encoder: Encoder, value: OptionalProperty<T>) {
when (value) {
is OptionalProperty.NotPresent -> {}
is OptionalProperty.Present -> valueSerializer.serialize(encoder, value.value)
}
}
}
#Serializable
private data class MyAddressNew(
var line1: OptionalProperty<String?> = OptionalProperty.NotPresent,
var line2: OptionalProperty<String?> = OptionalProperty.NotPresent,
var city: OptionalProperty<String?> = OptionalProperty.NotPresent,
var postal_code: OptionalProperty<String?> = OptionalProperty.NotPresent,
var state: OptionalProperty<String?> = OptionalProperty.NotPresent,
var country: OptionalProperty<String?> = OptionalProperty.NotPresent,
)
fun main() {
val jsonStringPOST = "{\"line1\":\"street\",\"country\":\"GB\"}"
println("JSON string is: $jsonStringPOST")
val myAddressPost = Json.decodeFromString<MyAddressNew>(jsonStringPOST)
println("MyAddress object: $myAddressPost")
val jsonStringUPDATE = "{\"country\":null}"
println("JSON string is: $jsonStringUPDATE")
val myAddressUpdate = Json.decodeFromString<MyAddressNew>(jsonStringUPDATE)
println("MyAddress object: $myAddressUpdate")
if(myAddressUpdate.country.isPresent()) {
println("Update country: ${myAddressUpdate.country}")
} else {
println("No update for country: ${myAddressUpdate.country}")
}
}
This prints:
JSON string is: {"line1":"street","country":"GB"}
MyAddress object: MyAddressNew(line1=street, line2=<NotPresent>, city=<NotPresent>, postal_code=<NotPresent>, state=<NotPresent>, country=GB)
JSON string is: {"country":null}
MyAddress object: MyAddressNew(line1=<NotPresent>, line2=<NotPresent>, city=<NotPresent>, postal_code=<NotPresent>, state=<NotPresent>, country=null)
Update country: null

How to convert String into an Int, in order to used as uid of RoomDatabase

I was deveopong a Kotlin App where I try to implement Room and I conection with an API with retrun diferents cats Images, which I load into the App.
My problems comes in order I have 2 Models of data: One for the API conection and another for the RoomDatabase.
RoomUnsplashPhoto.kt
package com.example.mvvm_retrofit_imagesearchapp.data
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
//Entity que almaceno en Room a partir
// de la imagen mostrada en el DetailFragment
#Entity
data class RoomUnsplashPhoto(
#PrimaryKey(autoGenerate = true) val uid: Int,
#ColumnInfo(name = "description") val description: String?,
#ColumnInfo(name = "url") val url: String?,
#ColumnInfo(name="user") val user:String?,
)
And the model of the API
UnsplashPhoto.kt
package com.example.mvvm_retrofit_imagesearchapp.data
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
#Parcelize
data class UnsplashPhoto(
val id: String?,
val description: String?,
val urls: UnsplashPhotoUrls,
val user: UnsplashUser
) : Parcelable {
#Parcelize
data class UnsplashPhotoUrls(
val raw: String,
val full: String,
val regular: String,
val small: String,
val thumb: String,
) : Parcelable
#Parcelize
data class UnsplashUser(
val name: String,
val username: String
) : Parcelable {
val attributionUrl get() =
"https://unsplash.com/$username?utm_source=ImageSearchApp&utm_medium=referral"
}
}
So as you can see the problem comes when I try to convert the id of the API which is an String, into an uid of my Room Model, which need to be a Int or Long, I guess
The error is the follow:
java.lang.NumberFormatException: For input string: "pdALzg0yN-8"
Finally the Fragment where I try to implement this is this:
package com.example.mvvm_retrofit_imagesearchapp.ui.detail
import android.content.Intent
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.navArgs
import androidx.room.Room
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.Target
import com.example.mvvm_retrofit_imagesearchapp.R
import com.example.mvvm_retrofit_imagesearchapp.data.RoomUnsplashPhoto
import com.example.mvvm_retrofit_imagesearchapp.data.UnsplashPhoto
import com.example.mvvm_retrofit_imagesearchapp.databinding.FragmentDetailBinding
import com.example.mvvm_retrofit_imagesearchapp.room.PhotoDatabase
import com.example.mvvm_retrofit_imagesearchapp.utils.Global
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.lang.NumberFormatException
class DetailFragment : Fragment(R.layout.fragment_detail){
//DetailFragmentArgs, lo crea el navigation.xml en la etiqueta <arguments>
private val args by navArgs<DetailFragmentArgs>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = FragmentDetailBinding.bind(view)
binding.apply {
val photo = args.photo
var room_photo : RoomUnsplashPhoto? = null
try{
room_photo = RoomUnsplashPhoto(photo.id!!?.toInt(),photo.description, photo.urls.full, photo.user.name)
}catch (e:NumberFormatException){
Toast.makeText(context, "¡ Falla la conversion !", Toast.LENGTH_LONG).show()
Log.d("NumberFormat", e.toString())
}
val ro : RequestOptions = RequestOptions()
Glide.with(this#DetailFragment)
.load(photo.urls.regular)
.error(R.drawable.ic_error)
.override(Target.SIZE_ORIGINAL/3)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?,model: Any?,target: Target<Drawable>?,isFirstResource: Boolean): Boolean {
progressBar.isVisible = false
return false
}
override fun onResourceReady(resource: Drawable?,model: Any?,target: Target<Drawable>?,dataSource: DataSource?,isFirstResource: Boolean): Boolean {
progressBar.isVisible = false
textviewDesc.isVisible = photo.description != null
textviewCreator.isVisible = true
return false
}
})
.into(imageView)
textviewDesc.text = photo.description
val uri = Uri.parse(photo.user.attributionUrl)
val intent = Intent(Intent.ACTION_VIEW, uri)
//set the listener to Room
val db = context?.let {
Room.databaseBuilder(
it.applicationContext,
PhotoDatabase::class.java, Global.databaseName
).build()
}
btnAdd.setOnClickListener(View.OnClickListener {
GlobalScope.launch {
if (room_photo != null) {
insertPhoto(room_photo, db)
}else{
Toast.makeText(context, "El room_photo es null", Toast.LENGTH_LONG).show()
}
//Pass all the data to the RoomDatabase.
}
})
textviewCreator.apply {
text = "Photo by ${photo.user.name} on Unsplash"
setOnClickListener {
context.startActivity(intent)
}
paint.isUnderlineText = true
}
}
}
fun insertPhoto(photo: RoomUnsplashPhoto, db: PhotoDatabase?) {
//Pass all the data to the RoomDatabase.
val resp = db?.photoDao()?.insertPhoto(RoomUnsplashPhoto(photo.uid,photo.description, photo.url, photo.user))
if (resp != null) {
if(resp.equals(photo.uid)){
Toast.makeText(context, "Foto añadida correctamente a su diario.", Toast.LENGTH_LONG).show()
}else{
Toast.makeText(context, "No se ha podido añadir la foto a su diario", Toast.LENGTH_LONG).show()
}
}
}
}
I hope you can help, and if like this take thanks in advance !
I'd suggest using a String is the primary key (assuming that the id/uid is going to be unique) so use :-
#Entity
data class RoomUnsplashPhoto(
#PrimaryKey val uid: String,
#ColumnInfo(name = "description") val description: String?,
#ColumnInfo(name = "url") val url: String?,
#ColumnInfo(name="user") val user:String?,
)
Or alternately you could have an INTEGER column for the id and a String column for the uid e.g. :-
#Entity( indices = [Index(value = ["uid"], unique = true)])
data class RoomUnsplashPhoto(
#PrimaryKey(autoGenerate = true) val id: Int,
val uid: String,
#ColumnInfo(name = "description") val description: String?,
#ColumnInfo(name = "url") val url: String?,
#ColumnInfo(name="user") val user:String?,
)
Note that the id column isn't really an extra column, it aliases the rowid column (a normally hidden column that always exists except for a WITHOUT ROWID table, which Room doesn't cater for anyway).
the index on the uid column would force the uid to be unique.
With either of the above, you do not need to convert the String to an Integer.

Kotlin create email client (javamail, Google OAUTH, kotlin) - AUTHENTICATIONFAILED invalid credentials (failure)

I'm trying to develop a small android email client in kotlin.
I use javanais api and google OAUTH but I got the following message.
-----TEST START-----
-----TEST BP1-----
-----TEST BP2-----
ERROR:javax.mail.AuthenticationFailedException: [AUTHENTICATIONFAILED] Invalid credentials (Failure)
ERROR MESSAGE : [AUTHENTICATIONFAILED] Invalid credentials (Failure)
ERROR MESSAGE : kotlin.Unit
The retrieval of the google oauth token works well but not the passing it to javanais api.
MainActivity.kt :
package fr.nothing.lab.emailtest
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.auth.api.signin.GoogleSignInClient
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.common.SignInButton
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.tasks.Task
class MainActivity : AppCompatActivity() {
private var mGoogleSignInClient: GoogleSignInClient? = null
private val RC_SIGN_IN = 9001
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.server_client_id))
.requestEmail()
.build()
mGoogleSignInClient = GoogleSignIn.getClient(this, gso)
}
public override fun onStart(){
super.onStart()
val mGmailSignIn = findViewById<SignInButton>(R.id.sign_in_button)
val account = GoogleSignIn.getLastSignedInAccount(this)
Log.w("MainActivity--Sign in 1:", account.toString())
mGmailSignIn.setOnClickListener {
signIn()
}
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(requestCode == RC_SIGN_IN){
val task = GoogleSignIn.getSignedInAccountFromIntent(data)
handleSignInResult(task)
}
}
private fun handleSignInResult(completedTask: Task<GoogleSignInAccount>){
Log.w("MainActivity -- Sign in 2:", completedTask.toString())
try{
val account = completedTask.getResult(ApiException::class.java)
val idToken = getString(R.string.server_client_id)
val email = account!!.email
val ee = account!!.isExpired
Log.w("MainActivity -- Sign in 4 :","Token id :"+idToken.toString() + " | "+email)
Log.w("MainActivity -- Sign in 4 :","email :"+email)
Log.w("MainActivity -- Sign in 4 :","expried :"+ee)
if(idToken != null){
val pop3Host = "imap.gmail.com"
val mailStoreType = "imaps"
val username = email.toString()
val password = idToken
val tp = threadperso(true,pop3Host, mailStoreType, username, password)
tp.start()
}
}catch(e: ApiException){
Log.w("MainActivity--Sign in 3:","signInResult:failed open =" + e.statusCode)
}
}
private fun signIn(){
val signInIntent = mGoogleSignInClient!!.signInIntent
startActivityForResult(signInIntent, RC_SIGN_IN)
}
}
thradperso.kt
package fr.nothing.lab.emailtest
import android.os.AsyncTask
class threadperso(
private val google : Boolean,
private val pop3Host : String,
private val storeType : String,
private val user : String,
private val password : String
) : Thread(){
public override fun run(){
ReceiveEmail.receiveEmail(google, pop3Host, storeType, user, password)
}
}
ReceiveEmail.kt
package fr.nothing.lab.emailtest
import android.util.Log
import java.util.*
import javax.mail.Address
import javax.mail.Folder
import javax.mail.Message
import javax.mail.Session
class ReceiveEmail {
companion object{
fun receiveEmail(
google : Boolean,
pop3Host : String,
storeType : String,
user : String,
password : String
){
val TAG = "MainActivity"
var prop = Properties()
//prop.put("mail.imaps.host", pop3Host)
//prop.put("mail.imaps.port","993")
//prop.put("mail.imaps.socketFactory.class","javax.net.ssl.SSLSocketFactory")
prop.put("mail.imaps.starttls.enable", "true")
prop.put("mail.imaps.auth", "true")
prop.put("mail.imaps.ssl.enable","true")
prop.put("mail.imaps.ssl.trust","*")
prop.put("mail.imaps.auth.mechanisms","XOAUTH2")
prop.put("mail.store.protocol","imaps")
var session = Session.getInstance(prop)
Log.w(TAG,"-----TEST START-----")
try {
Log.w(TAG,"-----TEST BP1-----")
//var mailStore = session.getStore(storeType)
var mailStore = session.getStore(storeType)
Log.w(TAG,"-----TEST BP2-----")
mailStore.connect(pop3Host, 993, user, password.toString())
Log.w(TAG,"-----TEST BP3-----")
var folder : Folder = mailStore.getFolder("INBOX")
Log.w(TAG,"-----TEST BP4-----")
folder.open(Folder.READ_ONLY)
Log.w(TAG,"-----TEST BP5-----")
var emailMessage = folder.getMessages()
Log.w(TAG,"Total Message - " + emailMessage.size)
var i = 1
for(mess : Message in emailMessage){
var toAddSize : Int = mess.getRecipients(Message.RecipientType.TO).size
var toAdress = mess.getRecipients(Message.RecipientType.TO)
Log.w(TAG,"-----TEST BP6-----")
Log.w(TAG,"EMAIL : "+ i.toString() + "/"+emailMessage+1)
Log.w(TAG,"Subject : "+ mess.subject)
Log.w(TAG,"From : "+mess.from[0])
}
folder.close()
mailStore.close()
}catch(e : Exception ){
Log.w(TAG, "ERROR:"+e.toString())
Log.w(TAG, "ERROR MESSAGE : " + e.message.toString())
Log.w(TAG, "ERROR MESSAGE : " + e.printStackTrace())
}
}
}
}

How to correctly do array request in volley

I'm trying to do get request and get array in json but i'm having error like this:
org.json.JSONException: Value [] of type org.json.JSONArray cannot be converted to JSONObject
here's my request code:
private fun getStudents(endLink: String) {
val request = JSONObject()
val studentLink = "https://192.168.1.1/getStudents.php?idEntity="
val linkFull = studentLink.plus(endLink)
val jsArrayRequest = JsonObjectRequest(Request.Method.GET, linkFull, request, Response.Listener<JSONObject> {
val builder = GsonBuilder()
val gson = builder.create()
val student =
gson.fromJson<Students>(it.toString(), students::class.java!!)
studentResponse = studentResponse
//updateInfo()
}, Response.ErrorListener {
Log.d("ERRORKA", it.message)
Toast.makeText(
this.context,
it.message, Toast.LENGTH_SHORT
).show()
})
MySingleTon.getInstance(this.context!!).addToRequestQue(jsArrayRequest)
}
also my data models:
class StudentResponse {
var groupNumber: String = ""
var students: List<Students>? = null
}
here's 2nd:
class Students {
val id: Int = 0
val firstName: String? = ""
val lastName: String? = ""
val middleName: String? = ""
val email: String? = ""
}
You are not doing jsonArrayRequest, you can see the object you are creating is of JsonObjectRequest. Do like below.
// Method: POST
val mDataArray = JSONArray(); // This should be JSONArray object
val mRequest = JsonArrayRequest(Request.Method.POST,"YOUR API URL", mDataArray,{
//Response listener
},{
//Error listener
})
// Method: GET
val mRequest = JsonArrayRequest(Request.Method.GET,"YOUR API URL", null,{
//Response listener
},{
//Error listener
})