Android Kotlin - retrofit2 set POST parameters - kotlin

interface ApiInterface {
#Headers("Content-Type: application/json")
#POST("testgetmemes/")
fun getMemes(): Call<List<Memes>>
}
object ApiClient {
var BASE_URL:String="https://www.androidisapos.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>>) {
setMemes(JSONArray(Gson().toJson(response.body())), gal)
}
override fun onFailure(call: Call<List<Memes>>?, t: Throwable?) {
Log.d(tagg, t!!.toString())
}
})
How can I add POST Parameters (and the values ofc)? I've seen countless examples but they all construct the code of this absolutely awful library differently what makes it impossible to understand when you don't know Kotlin/Java 100%
EDIT:
I tried:
fun getMemes(#Query("test") test: String?): Call<List<Memes>>
and
val call: Call<List<Memes>> = ApiClient.getClient.getMemes("bla")
It doesn't send POST key test with value bla

Related

How to use the information stored in mutableStateOf in Jetpack Compose

I have information in json and I retrieve it using retrofit2, everything works fine, I get the data in a List.
I need this information to fill elements in Jetpack Compose for which I use mutableStateOf to save the states.
My function that I use is the following:
fun jsonParsing(
dataRecox: MutableState<List<Event>>
) {
val TAG_LOGS = "Mariox"
val retrofit = Retrofit.Builder()
.baseUrl("http://myserversample.com/pGet/track/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val retrofitAPI = retrofit.create(APIService1::class.java)
retrofitAPI.getRecolector().enqueue(object : Callback<List<Event>> {
override fun onResponse(
call: Call<List<Event>>,
response: Response<List<Event>>
) {
val data = response.body()
val mydata = data!!
dataRecox.value = mydata
Log.i(TAG_LOGS, Gson().toJson(data))
}
override fun onFailure(call: Call<List<Event>>, t: Throwable) {
t.printStackTrace()
}
})
}
Mymodel:
data class Event (
val deviceID : Int,
val statusCode : Int,
val accountID : String,
val speedKPH : Int,
.
.
.
}
My composable:
#Composable
fun Greeting(name: String) {
val dataRecox = remember {
mutableStateOf(emptyList<Event>())
}
jsonParsing(dataRecox)
println("======")
println(dataRecox) // ok data
println(dataRecox.value). // ok data
//Uncommenting println(dataRecox.value[0]) I get empty.
//println(dataRecox.value[0])
//Text(text = dataRecox.value[0].uniqueID)
}
When I do not use the information in the console, by calling Greeting("Android") all the data is printed correctly:
The problem comes when I want to use that information:
For example, if I want to print in console println(dataRecox.value[0]) here it returns empty. If I want to use it with a composable Text: Text(text = dataRecox.value[0].uniqueID) it also gives me empty.
Can someone explain to me why this happens, because when I start using the information the data becomes empty.
The way you're doing is totally different of the recommended way... here's my suggestion.
Define a class to represent the screen's state.
data class ScreenState(
val events: List<Event> = emptyList(),
val error: Throwable? = null
)
Use a ViewModel to perform the API request and keep the screen state.
class EventsViewModel : ViewModel()
private val _screenState = MutableStateFlow<ScreenState>(ScreenState())
val screenState = _screenState.asStateFlow()
init {
jsonParsing()
}
fun jsonParsing() {
val TAG_LOGS = "Mariox"
val retrofit = Retrofit.Builder()
.baseUrl("http://myserversample.com/pGet/track/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val retrofitAPI = retrofit.create(APIService1::class.java)
retrofitAPI.getRecolector().enqueue(object : Callback<List<Event>> {
override fun onResponse(
call: Call<List<Event>>,
response: Response<List<Event>>
) {
val data = response.body()
Log.i(TAG_LOGS, Gson().toJson(data))
_screenState.update {
ScreenState(it.events)
}
}
override fun onFailure(call: Call<List<Event>>, t: Throwable) {
t.printStackTrace()
_screenState.update {
ScreenState(error = t)
}
}
})
}
}
Instantiate the ViewModel and use it in your screen...
#Composable
fun Greeting(name: String) {
val vm = viewModel<EventsViewModel>()
val screenState by vm.screenState.observeAsState()
LazyColumn(Modifier.fillMaxSize()) {
items(screenState.items) {
Text(it. accountID)
}
}
}

Custom multipart-form-data serializable in Kotlin/Ktor

Does anyone know how to program the override function convertForReceive of a custom Multipart.FormData converter?
I want to convert the multipart request to my class with the converter but I don't know how it works.
I have:
Application.kt
install(ContentNegotiation) {
json()
register(ContentType.MultiPart.FormData, CustomMultipartConverter)
}
CustomMultipartConverter
object CustomMultipartConverter: ContentConverter {
override suspend fun convertForReceive(context: PipelineContext<ApplicationReceiveRequest, ApplicationCall>): Any? {
TODO("Not yet implemented")
}
override suspend fun convertForSend(
context: PipelineContext<Any, ApplicationCall>,
contentType: ContentType,
value: Any
): Any? {
TODO("Not yet implemented")
}
}
REQUEST CLASS
class CreatePostRequest(
val text: String,
val image: File? = null
)
ROUTE
route("v1/posts") {
authenticate {
route("create") {
val authJWT = call.authentication.principal as JWTAtuh
val request = call.receive<CreatePostRequest>()
//myCode
call.respond(HttpStatusCode.OK)
}
}
}
You can take SerializationConverter as a reference:
override suspend fun convertForReceive(context: PipelineContext<ApplicationReceiveRequest, ApplicationCall>): Any? {
val request = context.subject
val channel = request.value as? ByteReadChannel ?: return null
val charset = context.call.request.contentCharset() ?: defaultCharset
val serializer = format.serializersModule.serializer(request.typeInfo)
val contentPacket = channel.readRemaining()
return when (format) {
is StringFormat -> format.decodeFromString(serializer, contentPacket.readText(charset))
is BinaryFormat -> format.decodeFromByteArray(serializer, contentPacket.readBytes())
else -> {
contentPacket.discard()
error("Unsupported format $format")
}
}
}

After calling retrofitCall.enqueue neither the OnFailed block is executing neither the OnResponse

When I click on the button, callRetrofit function begins execution, but the mycall.enqueue none of the onFailure or onResponse codeblocks are executing. There is nothing I can get from the logcat.
Here is my callRetrofit function:
private fun callRetrofit() {
val file = File(selectedFile.toString())
val filePrt = RequestBody.create(MediaType.parse("image/*"), file)
val MP2 = MultipartBody.Part.createFormData("Photo", "Tasveer", filePrt)
val retrofit = Retrofit.Builder()
.baseUrl("https://www.googleapis.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val jsonPlaceholderApi = retrofit.create(Jinterface::class.java)
val mycall = jsonPlaceholderApi.uploadFile(authoo, MP2)
mycall.enqueue(object : Callback<RequestBody?> {
override fun onFailure(call: Call<RequestBody?>, t: Throwable) {
Log.e("dikkat", t.message.toString())
}
override fun onResponse(call: Call<RequestBody?>, response: Response<RequestBody?>) {
Toast.makeText(this#MainActivity, "Donne", Toast.LENGTH_SHORT).show()
textView.text = response.body().toString()
Log.e("ressponse", response.body().toString())
}
})
}
And here's the interface:
interface Jinterface {
#Multipart
#POST("/upload/drive/v3/files?uploadType=media")
fun uploadFile(
#Header("Authorization") authorization: String,
#Part() file: MultipartBody.Part
) : Call<RequestBody>
}

Kotlin: How to fetch data from web service and display it in app?

Hello :) I'm working on app that shows popular movies and some details about each of them. I created a RecyclerView where information should be displayed. I'm stuck with getting and displaying data. I'm using https://www.themoviedb.org/ page for api.
I was following these steps: http://imakeanapp.com/make-a-movies-app-using-tmdb-api-part-4-networking-using-retrofit-library/ , but it's written in Java and I need code in Kotlin. I converted by myself a part of the code, here is what I have:
in Movie.kt
data class Movie (
#SerializedName("id") val id: Int,
#SerializedName("title") val title: String,
#SerializedName("poster_path") val posterPath: String,
#SerializedName("release_date") val releaseDate: String,
#SerializedName("vote_average") val rating: Float
)
in MoviesResponse.kt
data class MoviesResponse (
#SerializedName("page") val page: Int,
#SerializedName("total_results") val totalResults: Int,
#SerializedName("results") val movies: List<Movie>,
#SerializedName("total_pages") val totalPages: Int
)
in TMDbApi.kt
interface TMDbApi {
#GET("movie/popular")
fun getPopularMovies (
#Query("api_key") apiKey: String,
#Query("language") language: String,
#Query("page") page: Int
): Call<List<MoviesResponse>>
}
in OnGetMoviesCallback.kt
interface OnGetMoviesCallback {
fun onSuccess(movies: List<Movie>)
fun onError()
}
in MainAdapter.kt
class MainAdapter: RecyclerView.Adapter<CustomHolder>(){
var movies: List<Movie> = listOf()
fun MainAdapter(movies: List<Movie>) {
this.movies = movies
}
override fun getItemCount(): Int {
return movies.size
}
override fun onBindViewHolder(holder: CustomHolder, position: Int) {
holder.bind(movies.get(position))
holder?.setOnClick()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomHolder {
val layoutInflater = LayoutInflater.from(parent?.context)
val cellForRow = layoutInflater.inflate(R.layout.movie_row, parent, false)
return CustomHolder(cellForRow)
}
}
class CustomHolder(view: View): RecyclerView.ViewHolder(view){
fun bind(result: Movie){
itemView.title.text = result.title
itemView.release_date.text = result.releaseDate
}
}
in MoviesRepository.kt
val BASE_URL: String = "https://api.themoviedb.org/3/"
val LANGUAGE: String = "en-US"
data class MoviesRepository (
val repositroy: MoviesRepository,
val api: TMDbApi
)
object getInstance{
val retrofit: TMDbApi = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.client(OkHttpClient().newBuilder().build())
.baseUrl(BASE_URL)
.build()
.create(TMDbApi::class.java)
}
in AllMoviesActivity.kt
class AllMoviesActivity : AppCompatActivity(), Callback<List<MoviesResponse>> {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_all_movies)
rw_main.layoutManager = LinearLayoutManager(this)
rw_main.adapter = MainAdapter()
getMovies()
}
private fun getMovies() {
getInstance.retrofit.getPopularMovies("2B0b0e8d104f0d6130a4fc67848f89e107", LANGUAGE, 1).enqueue(this)
}
override fun onResponse(call: retrofit2.Call<List<MoviesResponse>>, response: Response<List<MoviesResponse>>) {
val moviesResponse = response.body() ?: listOf()
Log.d("Results", moviesResponse.toString())
}
override fun onFailure(call: retrofit2.Call<List<MoviesResponse>>, t: Throwable) {
Toast.makeText(this, "Failed", Toast.LENGTH_SHORT).show()
}
}
And I have added in Manifest file Internet permission. When I run this, I get 'Failed' Toast and I don't see RecyclerView. Can you tell me what I'm missing or what's wrong? I've been searching on the Internet solutions but with no results. Hope you could help me. Thanks!
Looks like you are missing something small. After making a call to the API on Postman, Here's what you expect on your response body:
{
"page": 1,
"total_results": 10000,
"total_pages": 500,
"results": [...
}
Having brought that up, lets digest your lines, in TMDbApi.kt, you have indicated that your expected response is a List<MoviesResponse> which conflicts the response you receive as it is a JSON of type MoviesResponse,
Fix:
...): Call<List<MoviesResponse>>
}
to:
): Call<MoviesResponse>
}
The rest are small fixes to align your response handling.
Head over to AllMoviesActivity.kt and change the following:
class AllMoviesActivity : AppCompatActivity(), Callback<List<MoviesResponse>> {
to
class MainActivity : AppCompatActivity(), Callback<MoviesResponse>
Then on the callback functions, change signatures like so:
override fun onResponse(call: retrofit2.Call<List<MoviesResponse>>, response: Response<List<MoviesResponse>>) {
val moviesResponse = response.body() ?: listOf() ...
to:
override fun onResponse(call: Call<MoviesResponse>, response:Response<MoviesResponse>
) {
val moviesResponse = response.body()//type will be inferred ...
Then finally,
override fun onFailure(call: retrofit2.Call<List<MoviesResponse>>, t: Throwable) {
to:
override fun onFailure(call: Call<MoviesResponse>, t: Throwable) {
That's the basic to get the response and keep the Failure toast at bay.
Parting shot
Make the logs your friend, they can reveal the messy & smallest misses

Kotlin, reduce duplicated code

Every my API service interface class have create static method,
interface AuthApiService {
#FormUrlEncoded
#POST("api/auth/login")
fun postLogin(#Field("username") username: String, #Field("password") password: String):
io.reactivex.Observable<LoginApiResponse>
companion object Factory {
fun create(): AuthApiService {
val gson = GsonBuilder().setLenient().create()
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl("http:192.168.24.188:8080")
.build()
return retrofit.create(AuthApiService::class.java)
}
}
}
interface BBBApiService {
companion object Factory {
fun create(): BBBApiService {
val gson = GsonBuilder().setLenient().create()
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl("http:192.168.24.188:8080")
.build()
return retrofit.create(BBBApiService::class.java)
}
}
}
But, I want to define the create() method only once.
So I made the ApiFactory class,
interface ApiFactory {
companion object {
inline fun <reified T>createRetrofit(): T {
val gson = GsonBuilder().setLenient().create()
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl("http://192.168.24.188:8080")
.build()
return retrofit.create(T::class.java)
}
}
}
interface AuthApiService {
#FormUrlEncoded
#POST("api/auth/login")
fun postLogin(#Field("username") username: String, #Field("password") password: String):
io.reactivex.Observable<LoginApiResponse>
companion object Factory {
fun create(): AuthApiService {
return ApiFactory.createRetrofit()
}
}
But, still, I need to define the create() method in AuthApiService.
Is there any a way implement the ApiFactory class to SubApi classes so that I don't have to define the create method in each child classes?
A simple solution is just to call the function of your ApiFactory directly:
val authApiService = ApiFactory.createRetrofit<AuthApiService>()
But if you want to be able to call AuthApiService.create(), then you can define a marker interface, say, ApiFactoryClient<T>, and mark an empty companion object with it.
interface ApiFactoryClient<T>
interface AuthApiService {
/* ... */
companion object : ApiFactoryClient<AuthApiService>
}
And then make an extension function that works with ApiFactoryClient<T>:
inline fun <reified T> ApiFactoryClient<T>.create(): T = ApiFactory.createRetrofit<T>()
And the usage would be:
val authApiService = AuthApiService.create()
You can modify your ApiFactory like this:
interface ApiFactory {
companion object {
inline fun <reified T>createRetrofit(klass: KClass<T>): T {
val gson = GsonBuilder().setLenient().create()
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl("http://192.168.24.188:8080")
.build()
return retrofit.create(klass.java)
}
}
}
And then use it to create different service instances:
val authApiService = ApiFactory.createRetrofit(AuthApiService::class)