I am a beginner in Kotlin. I need to send a variable parameter from my Activity to a Retrofit call.
This is my call in on Create of Detail Activity
override fun onCreate(savedInstanceState: Bundle?) {
//...
val id = intent.getStringExtra("id")
// Get the ViewMode
val mModel = ViewModelProviders.of(this).get(myObjectViewModel::class.java)
//Create the observer which updates the UI.
val myObjectByIdObserver = Observer<MyObject> { myObject->
//...
}
//Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
mModel.getObjectById.observe(this, myObjectByIdObserver)
}
Here I insert value hardcode, I need the parameter received from the previous Activity.
class MyObjectViewModel : ViewModel() {
//this is the data that we will fetch asynchronously
var myObject: MutableLiveData<MyObject>? = null
val getMyObjectById: LiveData<MyObject>
get() {
if (myObject == null) {
myObject = MutableLiveData()
loadMyObjectById()
}
return myObject as MutableLiveData<MyObject>
}
private fun loadMyObjectById() {
val retrofit = Retrofit.Builder()
.baseUrl(Api.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val api = retrofit.create(Api::class.java)
val call = api.myObjectById(100)
call.enqueue(object : Callback<MyObject> {
override fun onResponse(call: Call<MyObject>, response: Response<MyObject>) {
myObject!!.value = response.body()
}
override fun onFailure(call: Call<MyObject>, t: Throwable) {
var tt = t
}
})
}
My API:
interface Api {
companion object {
const val BASE_URL = "https://.../"
}
#GET("myObjects/{id}")
fun myObjectById(#Path("id") id: Int?): Call<MyObject>
}
You can do this by ``#Query``` annotation.
interface Api {
companion object {
const val BASE_URL = "https://.../"
}
#GET("myObjects/{id}")
fun myObjectById(#Path("id") id: Int?, #Query("a_param") aParam: String?): Call<MyObject>
}
Edited. I completely misread your intension.
What you need seems to be ViewModelProvider.NewInstanceFactory like
class MyObjectViewModel(val id: Int): ViewModel() {
class Factory(val id: Int) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MyObjectViewModel(id) as T
}
}
}
then
val myViewModel = ViewModelProviders
.of(this, MyObjectViewModel.Factory(id))
.get(MyObjectViewModel::class.java)
Related
what im trying to get is user list from https://randomuser.me/api/?results=50
using RetroFit , in mvvm pattren with live data
class MainActivity : AppCompatActivity() {
lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val quoteService = RetrofitHelper.getInstance().create(QuoteService::class.java)
val repository = QuoteRepository(quoteService)
mainViewModel = ViewModelProvider(this,MainViewModelFactory(repository)).get(MainViewModel::class.java)
mainViewModel.quotes.observe(this) {
Log.d("hello", it.results.toString())
}
}
}
used method
interface QuoteService {
#GET("/api")
suspend fun getQuotes(#Query("results") results: Int):Response<QuoteList>
// baseUrl+/api/ + ?results=1
}
**Tried this method also same issue **
interface QuoteService {
#GET("/api/?results=50")
suspend fun getQuotes()):Response<QuoteList>
// baseUrl+/api/ + ?results=1
}
RetrofitHelper
object RetrofitHelper {
private val baseUrl="https://randomuser.me/"
fun getInstance() : Retrofit{
return Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
Reposistory
class QuoteRepository(private val quoteService: QuoteService) {
private val quotesLiveData = MutableLiveData<QuoteList>()
val quotes:LiveData<QuoteList>
get() = quotesLiveData
suspend fun getQuotes(results:Int){
val result = quoteService.getQuotes(results)
if (result.body() != null){
quotesLiveData.postValue(result.body())
}
}
}
ViewModelIsLike
class MainViewModel(private val repository: QuoteRepository) :ViewModel() {
init {
viewModelScope.launch (Dispatchers.IO){
repository.getQuotes(20)**Here come my problem if i decrease the number or even i try to the normal way it occurs same problem **
}
}
val quotes:LiveData<QuoteList>
get() = repository.quotes
}
viewModelFactory
class MainViewModelFactory(private val repository: QuoteRepository):ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MainViewModel(repository) as T
}
}
**Have to show list of user in recycler view **
I think the problem is while parsing the JSON, Somewhere you have declared some number field as String.
I hope your query is resolved.
I'm currently making a sample project about diagrams. I'm starting to use MVVM architecture recently, and I got stuck when the response is null. I also checked the Mutable Live Data to make sure that it is calling the API. Here's some of my code and the error-tag:
Model.kt
data class Model(
#SerializedName("FID") val FID: Int,
#SerializedName("region") val region: String,
#SerializedName("positive") val positive: Float
) {
}
ModelWrap.kt
data class ModelWrap(#SerializedName("samplesAPI") val attributes: Model){
}
ApiClient.kt
object ApiClient {
var retrofitService: ApiInterface? = null
const val BASE_URL = "https://sampleapi.../"
fun getApiSample() : ApiInterface {
if (retrofitService == null){
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
retrofitService = retrofit.create(ApiInterface::class.java)
}
return retrofitService!!
}
}
ApiInterface.kt
interface ApiInterface {
#GET("samples")
fun getSampleData(): Call<List<ModelWrap>>
}
MainViewModel.kt
class MainViewModelconstructor(private val repository: ModelRepository) : ViewModel(){
val sampleList= MutableLiveData<List<ModelWrap>>()
val errorMessage = MutableLiveData<String>()
fun getSampleData(pieChart: PieChart){
val response = repository.getSampleData()
response.enqueue(object : Callback<List<ModelWrap>> {
override fun onResponse(
call: Call<List<ModelWrap>>,
response: Response<List<ModelWrap>>
) {
sampleList.postValue(response.body())
}
override fun onFailure(call: Call<List<ModelWrap>>, t: Throwable) {
errorMessage.postValue(t.message)
}
})
}
}
MainViewModelFactory.kt
class MainViewModelFactoryconstructor(private val repository: MainRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return if (modelClass.isAssignableFrom(MainViewModel::class.java)){
MainViewModel(this.repository) as T
} else {
throw IllegalArgumentException("Sample ViewModel Not Found")
}
}
}
MainRepository.kt
class MainRepository constructor(private val retrofitService: ApiInterface){
fun getSampleData() = retrofitService.getSampleData()
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var pieChart: PieChart
lateinit var sampleViewModel: MainViewModel
private val sampleService = ApiClient.getApiSample()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
pieChart = findViewById(R.id.PieChart)
sampleViewModel= ViewModelProvider(this, MainViewModelFactory(MainRepository(sampleService))).get(MainViewModel::class.java)
getPieChart(pieChart)
}
private fun getPieChart(pieCharts: PieChart) {
mainViewModel.mainList.observe(this, Observer {
Log.d("TAG sample" , "onCreate PieChart: $it")
Log.d("Tag Samples Response" , response.body().toString())
if (it != null) {
val sampleEntries: List<PieEntry> = ArrayList()
for ((attributes) in it!!) {
sampleEntries.toMutableList()
.add(PieEntry(attributes.positive, attributes.region))
//........................................................................
val description = Description()
description.text = "Samples Data"
pieChart.description = description
pieChart.invalidate()
}
}
})
mainViewModel.errorMessage.observe(this, Observer { })
mainViewModel.getSampleData(pieCharts)
}
}
and Lastly, here's some or log message:
V/InputMethodManager: Starting input: tba=android.view.inputmethod.EditorInfo#8b795c0 nm : com.example.diargram ic=null
D/Tag Sample Response: null
D/TAG Sample: onCreate PieChart: null
E/libc: Access denied finding property "ro.serialno"
V/StudioTransport: Agent command stream started.
V/StudioTransport: Transport agent connected to daemon.
I would appreciate it if someone can help me :D, Thank you
Finally, I found a solution for my problem:
I type the wrong endpoint inside the interface class and it should be like this:
interface ApiInterface {
#GET("sample")
fun getSampleData(): Call<List> }
When it comes to assigning the livedata to the view, based on my JSON I should call ArrayList instead of List
List item
Before :
val sampleEntries: List = ArrayList()
After :
val sampleEntries: ArrayList<PieEntry> = ArrayList()
I have a sealed class for state handling of my Retrofit responses. It's members take a generic type. I would like to get Retrofit to be able to return the proper object, but I am stuck at this error: Unable to create converter for com.my.app.DataResult<?> - Cannot serialize abstract class com.my.app.DataResult
This is my DataResult class:
sealed class DataResult<out T> {
data class Success<out T>(val data: T?) : DataResult<T>()
data class Error<out T>(val code: Int? = null, val error: Exception? = null) : DataResult<T>()
object NetworkError : DataResult<Nothing>()
fun isSuccess() = this is Success<*>
fun isError() = this is Error<*>
fun data() = if (isSuccess()) (this as Success<T>).data else null
}
fun successResult() = DataResult.Success(null)
fun <T> successResult(data: T?) = DataResult.Success(data)
fun errorResult() = DataResult.Error<Nothing>(null)
This is the rest of my current implementation:
class NetworkClient(private val httpClient: HttpClient) {
private val baseUrl: String = "some url"
private val retrofit = Retrofit.Builder()
.baseUrl(mockend)
.addCallAdapterFactory(MyCallAdapterFactory())
.addConverterFactory(MoshiConverterFactory.create())
.client(httpClient.get())
.build()
private val apiService: ApiService = retrofit.create(StaApiService::class.java)
suspend fun <T> sendGet(endPoint: EndPoint, input: String): DataResult<T> {
val result = apiService.sendGetRequest<T>(endPoint.stringValue, queryMapOf(Pair("query", input)))
when (result) {
// do stuff here?
}
return result
}
}
interface ApiService {
#GET
suspend fun <T> sendGetRequest(
#Url url: String,
#QueryMap parameters: Map<String, String>): DataResult<T>
#GET
suspend fun <T> sendGetListRequest(
#Url url: String,
#QueryMap parameters: Map<String, String>): DataResult<List<T>>
}
abstract class CallDelegate<TIn, TOut>(
protected val proxy: Call<TIn>
) : Call<TOut> {
override fun execute(): Response<TOut> = throw NotImplementedError()
final override fun enqueue(callback: Callback<TOut>) = enqueueImpl(callback)
final override fun clone(): Call<TOut> = cloneImpl()
override fun cancel() = proxy.cancel()
override fun request(): Request = proxy.request()
override fun isExecuted() = proxy.isExecuted
override fun isCanceled() = proxy.isCanceled
abstract fun enqueueImpl(callback: Callback<TOut>)
abstract fun cloneImpl(): Call<TOut>
}
class ResultCall<T>(proxy: Call<T>) : CallDelegate<T, DataResult<T>>(proxy) {
override fun enqueueImpl(callback: Callback<DataResult<T>>) = proxy.enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
val code = response.code()
val result: DataResult<T> = if (code in 200 until 300) {
val body = response.body()
DataResult.Success(body)
} else {
DataResult.Error(code)
}
callback.onResponse(this#ResultCall, Response.success(result))
}
override fun onFailure(call: Call<T>, t: Throwable) {
val result: DataResult<Nothing> = if (t is IOException) {
DataResult.NetworkError
} else {
DataResult.Error(null)
}
callback.onResponse(this#ResultCall, Response.success(result))
}
})
override fun cloneImpl() = ResultCall(proxy.clone())
}
class ResultAdapter(
private val type: Type
) : CallAdapter<Type, Call<DataResult<Type>>> {
override fun responseType() = type
override fun adapt(call: Call<Type>): Call<DataResult<Type>> = ResultCall(call)
}
class MyCallAdapterFactory : CallAdapter.Factory() {
override fun get(
returnType: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
) = when (getRawType(returnType)) {
Call::class.java -> {
val callType = getParameterUpperBound(0, returnType as ParameterizedType)
when (getRawType(callType)) {
Result::class.java -> {
val resultType = getParameterUpperBound(0, callType as ParameterizedType)
ResultAdapter(resultType)
}
else -> null
}
}
else -> null
}
}
The above code is largely inspired by this answer to another question,
but I'm trying to add Generics to the mix, so I don't have to put every request into the interface by hand. Is it possible or not? I have tried for hours, also tried to build an adapter for the sealed class but failed. Has someone a good resource how this can be done?
As you can also see in the code I'd like to also be able to receive lists. Any tips here are much appreciated too.
I was inspired by the writing of this adapter to Valery Katkov's answer answer
My Retrofit call adapter is able to transform the JSON of normal objects correctly, but when I expect from a call a List<Object>, Retrofit returns me a List<LinkedTreeMap>. It cannot parse Object within the list
Exception
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.example.networkcalladapter.Post
CallAdapter Factory And CallAdapter
class NetworkCallAdapterFactory : CallAdapter.Factory() {
override fun get(
returnType: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
) = when (getRawType(returnType)) {
Call::class.java -> {
val callType = getParameterUpperBound(0, returnType as ParameterizedType)
when (getRawType(callType)) {
ResponseNetwork::class.java -> {
require(callType is ParameterizedType){ "resource must be paramterized" }
val resultType = getParameterUpperBound(0, callType)
ResponseNetworkAdapter<Any>(getRawType(resultType))
}
else -> null
}
}
else -> null
}
}
class ResponseNetworkAdapter<T: Any>(
private val type: Type
) : CallAdapter<T, Call<ResponseNetwork<T>>> {
override fun responseType() = type
override fun adapt(call: Call<T>): Call<ResponseNetwork<T>> = ResponseNetworkCall(call)
}
abstract class CallDelegate<TIn, TOut>(
protected val proxy: Call<TIn>
) : Call<TOut> {
override fun execute(): Response<TOut> = throw NotImplementedError()
final override fun enqueue(callback: Callback<TOut>) = enqueueImpl(callback)
final override fun clone(): Call<TOut> = cloneImpl()
override fun cancel() = proxy.cancel()
override fun request(): Request = proxy.request()
override fun isExecuted() = proxy.isExecuted
override fun isCanceled() = proxy.isCanceled
abstract fun enqueueImpl(callback: Callback<TOut>)
abstract fun cloneImpl(): Call<TOut>
}
class ResponseNetworkCall<T: Any>(proxy: Call<T>) : CallDelegate<T, ResponseNetwork<T>>(proxy) {
override fun enqueueImpl(callback: Callback<ResponseNetwork<T>>) {
proxy.enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
callback.onResponse(this#ResponseNetworkCall, Response.success(ResponseNetwork.create(response)))
}
override fun onFailure(call: Call<T>, t: Throwable) {
callback.onResponse(this#ResponseNetworkCall, Response.success(ResponseNetwork.create(Exception(t))))
}
})
}
override fun cloneImpl() = ResponseNetworkCall(proxy.clone())
}
ResponseNetwork
sealed class ResponseNetwork<T> {
companion object {
fun <T> create(error: Exception): ResponseNetworkError<T> {
return ResponseNetworkError(error)
}
fun <T> create(response: Response<T>): ResponseNetwork<T> {
return if (response.isSuccessful) {
response.body()?.let {
ResponseNetworkSuccess(response.code(), response.headers(), it)
} ?: ResponseNetworkEmpty(
response.code(),
response.errorBody()?.string() ?: "unknown error"
)
} else {
val msg = response.errorBody()?.string()
ResponseNetworkError(Exception(msg))
}
}
}
}
data class ResponseNetworkSuccess<T>(
val code: Int,
val header: Headers,
val body: T
) : ResponseNetwork<T>()
data class ResponseNetworkEmpty<T>(
val code: Int,
val message: String
) : ResponseNetwork<T>()
data class ResponseNetworkError<T>(
val exception: Exception
) : ResponseNetwork<T>()
Remote Api
#GET("posts")
suspend fun getPost(): ResponseNetwork<List<Post>>
Retrofit
Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(NetworkCallAdapterFactory())
.build()
.create(RemoteApi::class.java)
Post Model
data class Post(val userId: Int,
val id: Int,
val title: String,
val body: String)
Someone understands why retrofit always comes back to me List<LinkedTreeMap> whenever I need a list from the network ?
can you replace your remote API with this and check it.
#GET("posts")
suspend fun getPost(): Deferred<Response<ResponseNetwork<List<Post>>>
i fixed my bug in NetworkCallAdapterFactory
ResponseNetworkAdapter<Any>((resultType))
I want to abstract the retrofit call, so I don't need to write the same boiler plate code when I need a request.
The abstraction
open class NetworkCall<T> {
lateinit var call: Call<T>
var result: MutableLiveData<Resource<T>> = MutableLiveData()
fun makeCall(call: Call<T>) {
this.call = call
val callBackKt = CallBackKt<T>()
callBackKt.result.value = Resource.loading(null)
this.call.enqueue(callBackKt)
result = callBackKt.result
}
class CallBackKt<T> : Callback<T> {
var result: MutableLiveData<Resource<T>> = MutableLiveData()
override fun onFailure(call: Call<T>, t: Throwable) {
result.value = Resource.error()//APIError()
t.printStackTrace()
}
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {
result.value = Resource.success(response.body())
}
else {
result.value = Resource.error()
}
}
}
}
Repository
class DetailsRepository(val application: Application) {
private val myListDao =
AppDatabase.getDatabase(application)?.MyListDao()
var movie: MutableLiveData<Resource<DetailsDTO>> = MutableLiveData()
fun getDetails(id: Int) {
val networkCall = NetworkCall<DetailsDTO>()
networkCall.makeCall(Apifactory.tmdbApi.getDetails(id))
movie = networkCall.result
}
}
ViewModel
class DetailsViewModel(application: Application) : AndroidViewModel(application) {
private val repository: DetailsRepository = DetailsRepository(application)
internal var movie: LiveData<Resource<DetailsDTO>> = repository.movie
fun insert(myListItem: MyListItem) {
repository.insert(myListItem)
}
fun fetchDetails(id: Int) {
repository.getDetails(id)
}
}
Activity
viewModel.movie.observe(this, Observer {
it?.apply {
detail_title.text = data?.title
}
})
Log with in every method says it's being called in right order and that the request is working fine. Althoght onChange is not being called.
fetchDetails called
getDetails called
makeCall called
onResponse successful, result.value.data = DetailsDTO(adult=false, backdrop_path=/y8lEIjYZCi2VFP4ixtHSn2klpth.jpg, ...
...
What would be done wrong?